Merge branch 'crypto-improvements' into new-state-store

This commit is contained in:
Damir Jelić 2020-12-22 16:18:46 +01:00
commit 8857335a7d
28 changed files with 2804 additions and 718 deletions

View file

@ -1,9 +1,20 @@
use std::{env, io, process::exit};
use std::{
env, io,
process::exit,
sync::{
atomic::{AtomicBool, Ordering},
Arc,
},
};
use url::Url;
use matrix_sdk::{
self, events::AnyToDeviceEvent, identifiers::UserId, Client, ClientConfig, LoopCtrl, Sas,
SyncSettings,
self,
events::{
room::message::MessageEventContent, AnySyncMessageEvent, AnySyncRoomEvent, AnyToDeviceEvent,
},
identifiers::UserId,
Client, ClientConfig, LoopCtrl, Sas, SyncSettings,
};
async fn wait_for_confirmation(client: Client, sas: Sas) {
@ -68,10 +79,13 @@ async fn login(
.await?;
let client_ref = &client;
let initial_sync = Arc::new(AtomicBool::from(true));
let initial_ref = &initial_sync;
client
.sync_with_callback(SyncSettings::new(), |response| async move {
let client = &client_ref;
let initial = &initial_ref;
for event in &response.to_device.events {
match event {
@ -114,6 +128,53 @@ async fn login(
}
}
if !initial.load(Ordering::SeqCst) {
for (_room_id, room_info) in response.rooms.join {
for event in room_info.timeline.events {
if let AnySyncRoomEvent::Message(event) = event {
match event {
AnySyncMessageEvent::RoomMessage(m) => {
if let MessageEventContent::VerificationRequest(_) = &m.content
{
let request = client
.get_verification_request(&m.event_id)
.await
.expect("Request object wasn't created");
request
.accept()
.await
.expect("Can't accept verification request");
}
}
AnySyncMessageEvent::KeyVerificationKey(e) => {
let sas = client
.get_verification(&e.content.relation.event_id.as_str())
.await
.expect("Sas object wasn't created");
tokio::spawn(wait_for_confirmation((*client).clone(), sas));
}
AnySyncMessageEvent::KeyVerificationMac(e) => {
let sas = client
.get_verification(&e.content.relation.event_id.as_str())
.await
.expect("Sas object wasn't created");
if sas.is_done() {
print_result(&sas);
print_devices(&e.sender, &client).await;
}
}
_ => (),
}
}
}
}
}
initial.store(false, Ordering::SeqCst);
LoopCtrl::Continue
})
.await;

View file

@ -48,7 +48,7 @@ use matrix_sdk_base::{
#[cfg(feature = "encryption")]
use matrix_sdk_base::crypto::{
decrypt_key_export, encrypt_key_export, olm::InboundGroupSession, store::CryptoStoreError,
AttachmentEncryptor, OutgoingRequests, ToDeviceRequest,
AttachmentEncryptor, OutgoingRequests, RoomMessageRequest, ToDeviceRequest,
};
/// Enum controlling if a loop running callbacks should continue or abort.
@ -122,6 +122,7 @@ use matrix_sdk_common::{
use crate::{
http_client::{client_with_config, HttpClient, HttpSend},
verification_request::VerificationRequest,
Error, OutgoingRequest, Result,
};
@ -1168,6 +1169,17 @@ impl Client {
Ok(())
}
pub(crate) async fn room_send_helper(
&self,
request: &RoomMessageRequest,
) -> Result<send_message_event::Response> {
let content = request.content.clone();
let txn_id = request.txn_id;
let room_id = &request.room_id;
self.room_send(&room_id, content, Some(txn_id)).await
}
/// Send a room message to the homeserver.
///
/// Returns the parsed response from the server.
@ -1475,7 +1487,10 @@ impl Client {
}
#[cfg(feature = "encryption")]
async fn send_to_device(&self, request: &ToDeviceRequest) -> Result<ToDeviceResponse> {
pub(crate) async fn send_to_device(
&self,
request: &ToDeviceRequest,
) -> Result<ToDeviceResponse> {
let txn_id_string = request.txn_id_string();
let request = RumaToDeviceRequest::new(
request.event_type.clone(),
@ -1757,6 +1772,14 @@ impl Client {
.unwrap();
}
}
OutgoingRequests::RoomMessage(request) => {
if let Ok(resp) = self.room_send_helper(request).await {
self.base_client
.mark_request_as_sent(&r.request_id(), &resp)
.await
.unwrap();
}
}
}
}
}
@ -1911,7 +1934,20 @@ impl Client {
.await
.map(|sas| Sas {
inner: sas,
http_client: self.http_client.clone(),
client: self.clone(),
})
}
/// Get a `VerificationRequest` object with the given flow id.
#[cfg(feature = "encryption")]
#[cfg_attr(feature = "docs", doc(cfg(encryption)))]
pub async fn get_verification_request(&self, flow_id: &EventId) -> Option<VerificationRequest> {
let olm = self.base_client.olm_machine().await?;
olm.get_verification_request(flow_id)
.map(|r| VerificationRequest {
inner: r,
client: self.clone(),
})
}
@ -1960,7 +1996,7 @@ impl Client {
Ok(device.map(|d| Device {
inner: d,
http_client: self.http_client.clone(),
client: self.clone(),
}))
}
@ -2077,7 +2113,7 @@ impl Client {
Ok(UserDevices {
inner: devices,
http_client: self.http_client.clone(),
client: self.clone(),
})
}
@ -2157,8 +2193,8 @@ impl Client {
let encrypt = move || -> Result<()> {
let export: String = encrypt_key_export(&keys, &passphrase, 500_000)?;
let mut file = std::fs::File::create(path).unwrap();
file.write_all(&export.into_bytes()).unwrap();
let mut file = std::fs::File::create(path)?;
file.write_all(&export.into_bytes())?;
Ok(())
};
@ -2222,6 +2258,7 @@ impl Client {
};
let task = tokio::task::spawn_blocking(decrypt);
// TODO remove this unwrap.
let import = task.await.expect("Task join error").unwrap();
Ok(olm.import_keys(import).await?)

View file

@ -18,18 +18,15 @@ use matrix_sdk_base::crypto::{
store::CryptoStoreError, Device as BaseDevice, LocalTrust, ReadOnlyDevice,
UserDevices as BaseUserDevices,
};
use matrix_sdk_common::{
api::r0::to_device::send_event_to_device::Request as ToDeviceRequest,
identifiers::{DeviceId, DeviceIdBox},
};
use matrix_sdk_common::identifiers::{DeviceId, DeviceIdBox};
use crate::{error::Result, http_client::HttpClient, Sas};
use crate::{error::Result, Client, Sas};
#[derive(Clone, Debug)]
/// A device represents a E2EE capable client of an user.
pub struct Device {
pub(crate) inner: BaseDevice,
pub(crate) http_client: HttpClient,
pub(crate) client: Client,
}
impl Deref for Device {
@ -66,14 +63,11 @@ impl Device {
/// ```
pub async fn start_verification(&self) -> Result<Sas> {
let (sas, request) = self.inner.start_verification().await?;
let txn_id_string = request.txn_id_string();
let request = ToDeviceRequest::new(request.event_type, &txn_id_string, request.messages);
self.http_client.send(request).await?;
self.client.send_to_device(&request).await?;
Ok(Sas {
inner: sas,
http_client: self.http_client.clone(),
client: self.client.clone(),
})
}
@ -102,7 +96,7 @@ impl Device {
#[derive(Debug)]
pub struct UserDevices {
pub(crate) inner: BaseUserDevices,
pub(crate) http_client: HttpClient,
pub(crate) client: Client,
}
impl UserDevices {
@ -110,7 +104,7 @@ impl UserDevices {
pub fn get(&self, device_id: &DeviceId) -> Option<Device> {
self.inner.get(device_id).map(|d| Device {
inner: d,
http_client: self.http_client.clone(),
client: self.client.clone(),
})
}
@ -121,11 +115,11 @@ impl UserDevices {
/// Iterator over all the devices of the user devices.
pub fn devices(&self) -> impl Iterator<Item = Device> + '_ {
let client = self.http_client.clone();
let client = self.client.clone();
self.inner.devices().map(move |d| Device {
inner: d,
http_client: client.clone(),
client: client.clone(),
})
}
}

View file

@ -82,6 +82,8 @@ mod http_client;
mod device;
#[cfg(feature = "encryption")]
mod sas;
#[cfg(feature = "encryption")]
mod verification_request;
pub use client::{Client, ClientConfig, LoopCtrl, SyncSettings};
#[cfg(feature = "encryption")]

View file

@ -12,43 +12,49 @@
// See the License for the specific language governing permissions and
// limitations under the License.
use matrix_sdk_base::crypto::{ReadOnlyDevice, Sas as BaseSas};
use matrix_sdk_common::api::r0::to_device::send_event_to_device::Request as ToDeviceRequest;
use matrix_sdk_base::crypto::{OutgoingVerificationRequest, ReadOnlyDevice, Sas as BaseSas};
use crate::{error::Result, http_client::HttpClient};
use crate::{error::Result, Client};
/// An object controling the interactive verification flow.
#[derive(Debug, Clone)]
pub struct Sas {
pub(crate) inner: BaseSas,
pub(crate) http_client: HttpClient,
pub(crate) client: Client,
}
impl Sas {
/// Accept the interactive verification flow.
pub async fn accept(&self) -> Result<()> {
if let Some(req) = self.inner.accept() {
let txn_id_string = req.txn_id_string();
let request = ToDeviceRequest::new(req.event_type, &txn_id_string, req.messages);
self.http_client.send(request).await?;
match req {
OutgoingVerificationRequest::ToDevice(r) => {
self.client.send_to_device(&r).await?;
}
OutgoingVerificationRequest::InRoom(_) => todo!(),
}
}
Ok(())
}
/// Confirm that the short auth strings match on both sides.
pub async fn confirm(&self) -> Result<()> {
let (to_device, signature) = self.inner.confirm().await?;
let (request, signature) = self.inner.confirm().await?;
if let Some(req) = to_device {
let txn_id_string = req.txn_id_string();
let request = ToDeviceRequest::new(req.event_type, &txn_id_string, req.messages);
match request {
Some(OutgoingVerificationRequest::InRoom(r)) => {
self.client.room_send_helper(&r).await?;
}
self.http_client.send(request).await?;
Some(OutgoingVerificationRequest::ToDevice(r)) => {
self.client.send_to_device(&r).await?;
}
None => (),
}
if let Some(s) = signature {
self.http_client.send(s).await?;
self.client.send(s).await?;
}
Ok(())
@ -56,12 +62,17 @@ impl Sas {
/// Cancel the interactive verification flow.
pub async fn cancel(&self) -> Result<()> {
if let Some(req) = self.inner.cancel() {
let txn_id_string = req.txn_id_string();
let request = ToDeviceRequest::new(req.event_type, &txn_id_string, req.messages);
self.http_client.send(request).await?;
if let Some(request) = self.inner.cancel() {
match request {
OutgoingVerificationRequest::ToDevice(r) => {
self.client.send_to_device(&r).await?;
}
OutgoingVerificationRequest::InRoom(r) => {
self.client.room_send_helper(&r).await?;
}
}
}
Ok(())
}

View file

@ -0,0 +1,41 @@
// 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 matrix_sdk_base::{
crypto::VerificationRequest as BaseVerificationRequest, events::AnyMessageEventContent,
};
use crate::{Client, Result};
/// An object controling the interactive verification flow.
#[derive(Debug, Clone)]
pub struct VerificationRequest {
pub(crate) inner: BaseVerificationRequest,
pub(crate) client: Client,
}
impl VerificationRequest {
/// Accept the interactive verification flow.
pub async fn accept(&self) -> Result<()> {
if let Some(content) = self.inner.accept() {
let content = AnyMessageEventContent::KeyVerificationReady(content);
self.client
.room_send(self.inner.room_id(), content, None)
.await?;
}
Ok(())
}
}

View file

@ -19,6 +19,10 @@ use serde_json::value::RawValue as RawJsonValue;
use crate::{
events::{
call::{
answer::AnswerEventContent, candidates::CandidatesEventContent,
hangup::HangupEventContent, invite::InviteEventContent,
},
custom::CustomEventContent,
fully_read::FullyReadEventContent,
ignored_user_list::IgnoredUserListEventContent,
@ -143,6 +147,12 @@ impl Emitter {
AnySyncMessageEvent::Custom(e) => {
self.on_custom_event(room, &CustomEvent::Message(e)).await
}
AnySyncMessageEvent::CallInvite(e) => self.on_room_call_invite(room, e).await,
AnySyncMessageEvent::CallAnswer(e) => self.on_room_call_answer(room, e).await,
AnySyncMessageEvent::CallCandidates(e) => {
self.on_room_call_candidates(room, e).await
}
AnySyncMessageEvent::CallHangup(e) => self.on_room_call_hangup(room, e).await,
_ => {}
},
AnySyncRoomEvent::RedactedState(_event) => {}
@ -320,6 +330,19 @@ pub trait EventEmitter: Send + Sync {
_: &SyncMessageEvent<FeedbackEventContent>,
) {
}
/// Fires when `Client` receives a `RoomEvent::CallInvite` event
async fn on_room_call_invite(&self, _: RoomState, _: &SyncMessageEvent<InviteEventContent>) {}
/// Fires when `Client` receives a `RoomEvent::CallAnswer` event
async fn on_room_call_answer(&self, _: RoomState, _: &SyncMessageEvent<AnswerEventContent>) {}
/// Fires when `Client` receives a `RoomEvent::CallCandidates` event
async fn on_room_call_candidates(
&self,
_: RoomState,
_: &SyncMessageEvent<CandidatesEventContent>,
) {
}
/// Fires when `Client` receives a `RoomEvent::CallHangup` event
async fn on_room_call_hangup(&self, _: RoomState, _: &SyncMessageEvent<HangupEventContent>) {}
/// Fires when `Client` receives a `RoomEvent::RoomRedaction` event.
async fn on_room_redaction(&self, _: RoomState, _: &SyncRedactionEvent) {}
/// Fires when `Client` receives a `RoomEvent::RoomPowerLevels` event.
@ -507,6 +530,34 @@ mod test {
) {
self.0.lock().await.push("feedback".to_string())
}
async fn on_room_call_invite(
&self,
_: RoomState,
_: &SyncMessageEvent<InviteEventContent>,
) {
self.0.lock().await.push("call invite".to_string())
}
async fn on_room_call_answer(
&self,
_: RoomState,
_: &SyncMessageEvent<AnswerEventContent>,
) {
self.0.lock().await.push("call answer".to_string())
}
async fn on_room_call_candidates(
&self,
_: RoomState,
_: &SyncMessageEvent<CandidatesEventContent>,
) {
self.0.lock().await.push("call candidates".to_string())
}
async fn on_room_call_hangup(
&self,
_: RoomState,
_: &SyncMessageEvent<HangupEventContent>,
) {
self.0.lock().await.push("call hangup".to_string())
}
async fn on_room_redaction(&self, _: RoomState, _: &SyncRedactionEvent) {
self.0.lock().await.push("redaction".to_string())
}
@ -797,4 +848,28 @@ mod test {
],
)
}
#[async_test]
async fn event_emitter_voip() {
let vec = Arc::new(Mutex::new(Vec::new()));
let test_vec = Arc::clone(&vec);
let emitter = Box::new(EvEmitterTest(vec));
let client = get_client().await;
client.add_event_emitter(emitter).await;
let response = sync_response(SyncResponseFile::Voip);
client.receive_sync_response(response).await.unwrap();
let v = test_vec.lock().await;
assert_eq!(
v.as_slice(),
[
"call invite",
"call answer",
"call candidates",
"call hangup",
],
)
}
}

View file

@ -21,7 +21,7 @@ js_int = "0.1.9"
[dependencies.ruma]
version = "0.0.1"
git = "https://github.com/ruma/ruma"
rev = "e8882fe8142d7b55ed4c8ccc6150946945f9e237"
rev = "1a4e9aa20abff4786dbb91a19fb72c1dfa4410a7"
features = ["client-api", "unstable-pre-spec"]
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]

View file

@ -42,6 +42,7 @@ use tracing::warn;
use crate::{
olm::{InboundGroupSession, PrivateCrossSigningIdentity, Session},
store::{Changes, DeviceChanges},
OutgoingVerificationRequest,
};
#[cfg(test)]
use crate::{OlmMachine, ReadOnlyAccount};
@ -91,9 +92,16 @@ impl Device {
///
/// Returns a `Sas` object and to-device request that needs to be sent out.
pub async fn start_verification(&self) -> StoreResult<(Sas, ToDeviceRequest)> {
self.verification_machine
let (sas, request) = self
.verification_machine
.start_sas(self.inner.clone())
.await
.await?;
if let OutgoingVerificationRequest::ToDevice(r) = request {
Ok((sas, r))
} else {
panic!("Invalid verification request type");
}
}
/// Get the Olm sessions that belong to this device.

View file

@ -51,6 +51,7 @@ pub use machine::OlmMachine;
pub use olm::EncryptionSettings;
pub(crate) use olm::ReadOnlyAccount;
pub use requests::{
IncomingResponse, KeysQueryRequest, OutgoingRequest, OutgoingRequests, ToDeviceRequest,
IncomingResponse, KeysQueryRequest, OutgoingRequest, OutgoingRequests,
OutgoingVerificationRequest, RoomMessageRequest, ToDeviceRequest,
};
pub use verification::Sas;
pub use verification::{Sas, VerificationRequest};

View file

@ -36,7 +36,8 @@ use matrix_sdk_common::{
ToDeviceEvent,
},
identifiers::{
DeviceId, DeviceIdBox, DeviceKeyAlgorithm, EventEncryptionAlgorithm, RoomId, UserId,
DeviceId, DeviceIdBox, DeviceKeyAlgorithm, EventEncryptionAlgorithm, EventId, RoomId,
UserId,
},
js_int::UInt,
locks::Mutex,
@ -44,8 +45,6 @@ use matrix_sdk_common::{
Raw,
};
#[cfg(feature = "sqlite_cryptostore")]
use crate::store::sqlite::SqliteStore;
use crate::{
error::{EventError, MegolmError, MegolmResult, OlmError, OlmResult},
identities::{Device, IdentityManager, UserDevices},
@ -64,6 +63,8 @@ use crate::{
verification::{Sas, VerificationMachine},
ToDeviceRequest,
};
#[cfg(feature = "sqlite_cryptostore")]
use crate::{store::sqlite::SqliteStore, verification::VerificationRequest};
/// State machine implementation of the Olm/Megolm encryption protocol used for
/// Matrix end to end encryption.
@ -321,6 +322,7 @@ impl OlmMachine {
}
requests.append(&mut self.outgoing_to_device_requests());
requests.append(&mut self.verification_machine.outgoing_room_message_requests());
requests.append(&mut self.key_request_machine.outgoing_to_device_requests());
requests
@ -359,6 +361,9 @@ impl OlmMachine {
IncomingResponse::SignatureUpload(_) => {
self.verification_machine.mark_request_as_sent(request_id);
}
IncomingResponse::RoomMessage(_) => {
self.verification_machine.mark_request_as_sent(request_id);
}
};
Ok(())
@ -738,8 +743,8 @@ impl OlmMachine {
}
}
async fn handle_verification_event(&self, mut event: &mut AnyToDeviceEvent) {
if let Err(e) = self.verification_machine.receive_event(&mut event).await {
async fn handle_verification_event(&self, event: &AnyToDeviceEvent) {
if let Err(e) = self.verification_machine.receive_event(&event).await {
error!("Error handling a verification event: {:?}", e);
}
}
@ -767,6 +772,11 @@ impl OlmMachine {
self.verification_machine.get_sas(flow_id)
}
/// Get a verification request object with the given flow id.
pub fn get_verification_request(&self, flow_id: &EventId) -> Option<VerificationRequest> {
self.verification_machine.get_request(flow_id)
}
async fn update_one_time_key_count(&self, key_count: &BTreeMap<DeviceKeyAlgorithm, UInt>) {
self.account.update_uploaded_key_count(key_count).await;
}
@ -869,7 +879,7 @@ impl OlmMachine {
| AnyToDeviceEvent::KeyVerificationMac(..)
| AnyToDeviceEvent::KeyVerificationRequest(..)
| AnyToDeviceEvent::KeyVerificationStart(..) => {
self.handle_verification_event(&mut event).await;
self.handle_verification_event(&event).await;
}
_ => continue,
}
@ -924,6 +934,12 @@ impl OlmMachine {
// TODO set the encryption info on the event (is it verified, was it
// decrypted, sender key...)
if let Ok(e) = decrypted_event.deserialize() {
self.verification_machine
.receive_room_event(room_id, &e)
.await?;
}
Ok(decrypted_event)
}
@ -1810,34 +1826,34 @@ pub(crate) mod test {
let (alice_sas, request) = bob_device.start_verification().await.unwrap();
let mut event = request_to_event(alice.user_id(), &request);
bob.handle_verification_event(&mut event).await;
let event = request_to_event(alice.user_id(), &request.into());
bob.handle_verification_event(&event).await;
let bob_sas = bob.get_verification(alice_sas.flow_id()).unwrap();
let bob_sas = bob.get_verification(alice_sas.flow_id().as_str()).unwrap();
assert!(alice_sas.emoji().is_none());
assert!(bob_sas.emoji().is_none());
let mut event = bob_sas
let event = bob_sas
.accept()
.map(|r| request_to_event(bob.user_id(), &r))
.unwrap();
alice.handle_verification_event(&mut event).await;
alice.handle_verification_event(&event).await;
let mut event = alice
let event = alice
.outgoing_to_device_requests()
.first()
.map(|r| outgoing_request_to_event(alice.user_id(), r))
.unwrap();
bob.handle_verification_event(&mut event).await;
bob.handle_verification_event(&event).await;
let mut event = bob
let event = bob
.outgoing_to_device_requests()
.first()
.map(|r| outgoing_request_to_event(bob.user_id(), r))
.unwrap();
alice.handle_verification_event(&mut event).await;
alice.handle_verification_event(&event).await;
assert!(alice_sas.emoji().is_some());
assert!(bob_sas.emoji().is_some());
@ -1845,19 +1861,19 @@ pub(crate) mod test {
assert_eq!(alice_sas.emoji(), bob_sas.emoji());
assert_eq!(alice_sas.decimals(), bob_sas.decimals());
let mut event = bob_sas
let event = bob_sas
.confirm()
.await
.unwrap()
.0
.map(|r| request_to_event(bob.user_id(), &r))
.unwrap();
alice.handle_verification_event(&mut event).await;
alice.handle_verification_event(&event).await;
assert!(!alice_sas.is_done());
assert!(!bob_sas.is_done());
let mut event = alice_sas
let event = alice_sas
.confirm()
.await
.unwrap()
@ -1875,7 +1891,7 @@ pub(crate) mod test {
.unwrap();
assert!(!alice_device.is_trusted());
bob.handle_verification_event(&mut event).await;
bob.handle_verification_event(&event).await;
assert!(bob_sas.is_done());
assert!(alice_device.is_trusted());
}

View file

@ -43,7 +43,7 @@ use matrix_sdk_common::{
instant::Instant,
js_int::UInt,
locks::Mutex,
Raw,
CanonicalJsonValue, Raw,
};
use olm_rs::{
account::{IdentityKeys, OlmAccount, OneTimeKeys},
@ -743,7 +743,7 @@ impl ReadOnlyAccount {
.or_insert_with(BTreeMap::new)
.insert(
DeviceKeyId::from_parts(DeviceKeyAlgorithm::Ed25519, &self.device_id),
self.sign_json(&json_device_keys).await,
self.sign_json(json_device_keys).await,
);
device_keys
@ -770,8 +770,10 @@ impl ReadOnlyAccount {
/// # Panic
///
/// Panics if the json value can't be serialized.
pub async fn sign_json(&self, json: &Value) -> String {
self.sign(&json.to_string()).await
pub async fn sign_json(&self, json: Value) -> String {
let canonical_json: CanonicalJsonValue =
json.try_into().expect("Can't canonicalize the json value");
self.sign(&canonical_json.to_string()).await
}
pub(crate) async fn signed_one_time_keys_helper(
@ -785,7 +787,7 @@ impl ReadOnlyAccount {
"key": key,
});
let signature = self.sign_json(&key_json).await;
let signature = self.sign_json(key_json).await;
let mut signature_map = BTreeMap::new();

View file

@ -13,7 +13,14 @@
// limitations under the License.
use dashmap::{DashMap, DashSet};
use matrix_sdk_common::{api::r0::to_device::DeviceIdOrAllDevices, uuid::Uuid};
use matrix_sdk_common::{
api::r0::to_device::DeviceIdOrAllDevices,
events::room::{
encrypted::{MegolmV1AesSha2Content, MegolmV1AesSha2ContentInit},
message::Relation,
},
uuid::Uuid,
};
use std::{
cmp::min,
fmt,
@ -240,19 +247,31 @@ impl OutboundGroupSession {
"type": content.event_type(),
});
let relates_to: Option<Relation> = json_content
.get("content")
.map(|c| {
c.get("m.relates_to")
.cloned()
.map(|r| serde_json::from_value(r).ok())
})
.flatten()
.flatten();
let plaintext = json_content.to_string();
let ciphertext = self.encrypt_helper(plaintext).await;
EncryptedEventContent::MegolmV1AesSha2(
matrix_sdk_common::events::room::encrypted::MegolmV1AesSha2ContentInit {
ciphertext,
sender_key: self.account_identity_keys.curve25519().to_owned(),
session_id: self.session_id().to_owned(),
device_id: (&*self.device_id).to_owned(),
}
.into(),
)
let mut encrypted_content: MegolmV1AesSha2Content = MegolmV1AesSha2ContentInit {
ciphertext,
sender_key: self.account_identity_keys.curve25519().to_owned(),
session_id: self.session_id().to_owned(),
device_id: (&*self.device_id).to_owned(),
}
.into();
encrypted_content.relates_to = relates_to;
EncryptedEventContent::MegolmV1AesSha2(encrypted_content)
}
/// Check if the session has expired and if it should be rotated.

View file

@ -214,7 +214,7 @@ impl PrivateCrossSigningIdentity {
master.cross_signing_key(account.user_id().to_owned(), KeyUsage::Master);
let signature = account
.sign_json(
&serde_json::to_value(&public_key)
serde_json::to_value(&public_key)
.expect("Can't convert own public master key to json"),
)
.await;

View file

@ -23,7 +23,7 @@ use matrix_sdk_common::{
};
use serde::{Deserialize, Serialize};
use serde_json::{json, Error as JsonError, Value};
use std::{collections::BTreeMap, sync::Arc};
use std::{collections::BTreeMap, convert::TryInto, sync::Arc};
use thiserror::Error;
use zeroize::Zeroizing;
@ -36,6 +36,7 @@ use matrix_sdk_common::{
api::r0::keys::{CrossSigningKey, KeyUsage},
identifiers::UserId,
locks::Mutex,
CanonicalJsonValue,
};
use crate::{
@ -404,8 +405,9 @@ impl Signing {
pub async fn sign_json(&self, mut json: Value) -> Result<Signature, SignatureError> {
let json_object = json.as_object_mut().ok_or(SignatureError::NotAnObject)?;
let _ = json_object.remove("signatures");
let canonical_json = serde_json::to_string(json_object)?;
Ok(self.sign(&canonical_json).await)
let canonical_json: CanonicalJsonValue =
json.try_into().expect("Can't canonicalize the json value");
Ok(self.sign(&canonical_json.to_string()).await)
}
pub async fn sign(&self, message: &str) -> Signature {

View file

@ -14,8 +14,12 @@
use olm_rs::utility::OlmUtility;
use serde_json::Value;
use std::convert::TryInto;
use matrix_sdk_common::identifiers::{DeviceKeyAlgorithm, DeviceKeyId, UserId};
use matrix_sdk_common::{
identifiers::{DeviceKeyAlgorithm, DeviceKeyId, UserId},
CanonicalJsonValue,
};
use crate::error::SignatureError;
@ -63,11 +67,12 @@ impl Utility {
let unsigned = json_object.remove("unsigned");
let signatures = json_object.remove("signatures");
let canonical_json = serde_json::to_string(json_object)?;
let canonical_json: CanonicalJsonValue = json
.clone()
.try_into()
.map_err(|_| SignatureError::NotAnObject)?;
if let Some(u) = unsigned {
json_object.insert("unsigned".to_string(), u);
}
let canonical_json: String = canonical_json.to_string();
let signatures = signatures.ok_or(SignatureError::NoSignatureFound)?;
let signature_object = signatures
@ -81,18 +86,66 @@ impl Utility {
.ok_or(SignatureError::NoSignatureFound)?;
let signature = signature.as_str().ok_or(SignatureError::NoSignatureFound)?;
let ret = if self
let ret = match self
.inner
.ed25519_verify(signing_key, &canonical_json, signature)
.is_ok()
{
Ok(())
} else {
Err(SignatureError::VerificationError)
Ok(_) => Ok(()),
Err(_) => Err(SignatureError::VerificationError),
};
let json_object = json.as_object_mut().ok_or(SignatureError::NotAnObject)?;
if let Some(u) = unsigned {
json_object.insert("unsigned".to_string(), u);
}
json_object.insert("signatures".to_string(), signatures);
ret
}
}
#[cfg(test)]
mod test {
use super::Utility;
use matrix_sdk_common::identifiers::{user_id, DeviceKeyAlgorithm, DeviceKeyId};
use serde_json::json;
#[test]
fn signature_test() {
let mut device_keys = json!({
"device_id": "GBEWHQOYGS",
"algorithms": [
"m.olm.v1.curve25519-aes-sha2",
"m.megolm.v1.aes-sha2"
],
"keys": {
"curve25519:GBEWHQOYGS": "F8QhZ0Z1rjtWrQOblMDgZtEX5x1UrG7sZ2Kk3xliNAU",
"ed25519:GBEWHQOYGS": "n469gw7zm+KW+JsFIJKnFVvCKU14HwQyocggcCIQgZY"
},
"signatures": {
"@example:localhost": {
"ed25519:GBEWHQOYGS": "OlF2REsqjYdAfr04ONx8VS/5cB7KjrWYRlLF4eUm2foAiQL/RAfsjsa2JXZeoOHh6vEualZHbWlod49OewVqBg"
}
},
"unsigned": {
"device_display_name": "Weechat-Matrix-rs"
},
"user_id": "@example:localhost"
});
let signing_key = "n469gw7zm+KW+JsFIJKnFVvCKU14HwQyocggcCIQgZY";
let utility = Utility::new();
utility
.verify_json(
&user_id!("@example:localhost"),
&DeviceKeyId::from_parts(DeviceKeyAlgorithm::Ed25519, "GBEWHQOYGS".into()),
&signing_key,
&mut device_keys,
)
.expect("Can't verify device keys");
}
}

View file

@ -12,6 +12,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.
#![allow(missing_docs)]
use std::{collections::BTreeMap, sync::Arc, time::Duration};
use matrix_sdk_common::{
@ -26,10 +28,11 @@ use matrix_sdk_common::{
upload_signing_keys::Response as SigningKeysUploadResponse,
CrossSigningKey,
},
message::send_message_event::Response as RoomMessageResponse,
to_device::{send_event_to_device::Response as ToDeviceResponse, DeviceIdOrAllDevices},
},
events::EventType,
identifiers::{DeviceIdBox, UserId},
events::{AnyMessageEventContent, EventType},
identifiers::{DeviceIdBox, RoomId, UserId},
uuid::Uuid,
};
@ -120,6 +123,7 @@ pub enum OutgoingRequests {
/// Signature upload request, this request is used after a successful device
/// or user verification is done.
SignatureUpload(SignatureUploadRequest),
RoomMessage(RoomMessageRequest),
}
#[cfg(test)]
@ -150,12 +154,36 @@ impl From<ToDeviceRequest> for OutgoingRequests {
}
}
impl From<RoomMessageRequest> for OutgoingRequests {
fn from(request: RoomMessageRequest) -> Self {
OutgoingRequests::RoomMessage(request)
}
}
impl From<SignatureUploadRequest> for OutgoingRequests {
fn from(request: SignatureUploadRequest) -> Self {
OutgoingRequests::SignatureUpload(request)
}
}
impl From<OutgoingVerificationRequest> for OutgoingRequest {
fn from(r: OutgoingVerificationRequest) -> Self {
Self {
request_id: r.request_id(),
request: Arc::new(r.into()),
}
}
}
impl From<SignatureUploadRequest> for OutgoingRequest {
fn from(r: SignatureUploadRequest) -> Self {
Self {
request_id: Uuid::new_v4(),
request: Arc::new(r.into()),
}
}
}
/// Enum over all the incoming responses we need to receive.
#[derive(Debug)]
pub enum IncomingResponse<'a> {
@ -176,6 +204,7 @@ pub enum IncomingResponse<'a> {
/// The cross signing keys upload response, marking our private cross
/// signing identity as shared.
SignatureUpload(&'a SignatureUploadResponse),
RoomMessage(&'a RoomMessageResponse),
}
impl<'a> From<&'a KeysUploadResponse> for IncomingResponse<'a> {
@ -196,6 +225,12 @@ impl<'a> From<&'a ToDeviceResponse> for IncomingResponse<'a> {
}
}
impl<'a> From<&'a RoomMessageResponse> for IncomingResponse<'a> {
fn from(response: &'a RoomMessageResponse) -> Self {
IncomingResponse::RoomMessage(response)
}
}
impl<'a> From<&'a KeysClaimResponse> for IncomingResponse<'a> {
fn from(response: &'a KeysClaimResponse) -> Self {
IncomingResponse::KeysClaim(response)
@ -230,3 +265,55 @@ impl OutgoingRequest {
&self.request
}
}
#[derive(Clone, Debug)]
pub struct RoomMessageRequest {
/// The room to send the event to.
pub room_id: RoomId,
/// The transaction ID for this event.
///
/// Clients should generate an ID unique across requests with the
/// same access token; it will be used by the server to ensure
/// idempotency of requests.
pub txn_id: Uuid,
/// The event content to send.
pub content: AnyMessageEventContent,
}
#[derive(Clone, Debug)]
pub enum OutgoingVerificationRequest {
ToDevice(ToDeviceRequest),
InRoom(RoomMessageRequest),
}
impl OutgoingVerificationRequest {
pub fn request_id(&self) -> Uuid {
match self {
OutgoingVerificationRequest::ToDevice(t) => t.txn_id,
OutgoingVerificationRequest::InRoom(r) => r.txn_id,
}
}
}
impl From<ToDeviceRequest> for OutgoingVerificationRequest {
fn from(r: ToDeviceRequest) -> Self {
OutgoingVerificationRequest::ToDevice(r)
}
}
impl From<RoomMessageRequest> for OutgoingVerificationRequest {
fn from(r: RoomMessageRequest) -> Self {
OutgoingVerificationRequest::InRoom(r)
}
}
impl From<OutgoingVerificationRequest> for OutgoingRequests {
fn from(request: OutgoingVerificationRequest) -> Self {
match request {
OutgoingVerificationRequest::ToDevice(r) => OutgoingRequests::ToDeviceRequest(r),
OutgoingVerificationRequest::InRoom(r) => OutgoingRequests::RoomMessage(r),
}
}
}

View file

@ -12,25 +12,32 @@
// See the License for the specific language governing permissions and
// limitations under the License.
use std::sync::Arc;
use std::{convert::TryFrom, sync::Arc};
use dashmap::DashMap;
use matrix_sdk_common::locks::Mutex;
use tracing::{trace, warn};
use tracing::{info, trace, warn};
use matrix_sdk_common::{
events::{AnyToDeviceEvent, AnyToDeviceEventContent},
identifiers::{DeviceId, UserId},
events::{
room::message::MessageEventContent, AnyMessageEvent, AnySyncMessageEvent, AnySyncRoomEvent,
AnyToDeviceEvent,
},
identifiers::{DeviceId, EventId, RoomId, UserId},
locks::Mutex,
uuid::Uuid,
};
use super::sas::{content_to_request, Sas, VerificationResult};
use super::{
requests::VerificationRequest,
sas::{content_to_request, OutgoingContent, Sas, VerificationResult},
};
use crate::{
olm::PrivateCrossSigningIdentity,
requests::{OutgoingRequest, ToDeviceRequest},
requests::OutgoingRequest,
store::{CryptoStore, CryptoStoreError},
ReadOnlyAccount, ReadOnlyDevice,
OutgoingVerificationRequest, ReadOnlyAccount, ReadOnlyDevice, RoomMessageRequest,
};
#[derive(Clone, Debug)]
@ -39,7 +46,10 @@ pub struct VerificationMachine {
private_identity: Arc<Mutex<PrivateCrossSigningIdentity>>,
pub(crate) store: Arc<Box<dyn CryptoStore>>,
verifications: Arc<DashMap<String, Sas>>,
room_verifications: Arc<DashMap<EventId, Sas>>,
requests: Arc<DashMap<EventId, VerificationRequest>>,
outgoing_to_device_messages: Arc<DashMap<Uuid, OutgoingRequest>>,
outgoing_room_messages: Arc<DashMap<Uuid, OutgoingRequest>>,
}
impl VerificationMachine {
@ -52,15 +62,18 @@ impl VerificationMachine {
account,
private_identity: identity,
store,
verifications: Arc::new(DashMap::new()),
outgoing_to_device_messages: Arc::new(DashMap::new()),
verifications: DashMap::new().into(),
requests: DashMap::new().into(),
outgoing_to_device_messages: DashMap::new().into(),
room_verifications: DashMap::new().into(),
outgoing_room_messages: DashMap::new().into(),
}
}
pub async fn start_sas(
&self,
device: ReadOnlyDevice,
) -> Result<(Sas, ToDeviceRequest), CryptoStoreError> {
) -> Result<(Sas, OutgoingVerificationRequest), CryptoStoreError> {
let identity = self.store.get_user_identity(device.user_id()).await?;
let private_identity = self.private_identity.lock().await.clone();
@ -72,50 +85,111 @@ impl VerificationMachine {
identity,
);
let request = content_to_request(
device.user_id(),
device.device_id(),
AnyToDeviceEventContent::KeyVerificationStart(content),
);
let request = match content.into() {
OutgoingContent::Room(r, c) => RoomMessageRequest {
room_id: r,
txn_id: Uuid::new_v4(),
content: c,
}
.into(),
OutgoingContent::ToDevice(c) => {
let request = content_to_request(device.user_id(), device.device_id(), c);
self.verifications
.insert(sas.flow_id().to_owned(), sas.clone());
self.verifications
.insert(sas.flow_id().as_str().to_owned(), sas.clone());
request.into()
}
};
Ok((sas, request))
}
pub fn get_sas(&self, transaction_id: &str) -> Option<Sas> {
pub fn get_request(&self, flow_id: &EventId) -> Option<VerificationRequest> {
#[allow(clippy::map_clone)]
self.verifications.get(transaction_id).map(|s| s.clone())
self.requests.get(flow_id).map(|s| s.clone())
}
pub fn get_sas(&self, transaction_id: &str) -> Option<Sas> {
let sas = if let Ok(e) = EventId::try_from(transaction_id) {
#[allow(clippy::map_clone)]
self.room_verifications.get(&e).map(|s| s.clone())
} else {
None
};
if sas.is_some() {
sas
} else {
#[allow(clippy::map_clone)]
self.verifications.get(transaction_id).map(|s| s.clone())
}
}
fn queue_up_content(
&self,
recipient: &UserId,
recipient_device: &DeviceId,
content: AnyToDeviceEventContent,
content: OutgoingContent,
) {
let request = content_to_request(recipient, recipient_device, content);
let request_id = request.txn_id;
match content {
OutgoingContent::ToDevice(c) => {
let request = content_to_request(recipient, recipient_device, c);
let request_id = request.txn_id;
let request = OutgoingRequest {
request_id,
request: Arc::new(request.into()),
};
let request = OutgoingRequest {
request_id,
request: Arc::new(request.into()),
};
self.outgoing_to_device_messages.insert(request_id, request);
self.outgoing_to_device_messages.insert(request_id, request);
}
OutgoingContent::Room(r, c) => {
let request_id = Uuid::new_v4();
let request = OutgoingRequest {
request: Arc::new(
RoomMessageRequest {
room_id: r,
txn_id: request_id,
content: c,
}
.into(),
),
request_id,
};
self.outgoing_room_messages.insert(request_id, request);
}
}
}
fn receive_event_helper(&self, sas: &Sas, event: &mut AnyToDeviceEvent) {
#[allow(dead_code)]
fn receive_room_event_helper(&self, sas: &Sas, event: &AnyMessageEvent) {
if let Some(c) = sas.receive_room_event(event) {
self.queue_up_content(sas.other_user_id(), sas.other_device_id(), c);
}
}
fn receive_event_helper(&self, sas: &Sas, event: &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_request_as_sent(&self, uuid: &Uuid) {
self.outgoing_room_messages.remove(uuid);
self.outgoing_to_device_messages.remove(uuid);
}
pub fn outgoing_room_message_requests(&self) -> Vec<OutgoingRequest> {
self.outgoing_room_messages
.iter()
.map(|r| (*r).clone())
.collect()
}
pub fn outgoing_to_device_requests(&self) -> Vec<OutgoingRequest> {
#[allow(clippy::map_clone)]
self.outgoing_to_device_messages
@ -131,9 +205,9 @@ impl VerificationMachine {
for sas in self.verifications.iter() {
if let Some(r) = sas.cancel_if_timed_out() {
self.outgoing_to_device_messages.insert(
r.txn_id,
r.request_id(),
OutgoingRequest {
request_id: r.txn_id,
request_id: r.request_id(),
request: Arc::new(r.into()),
},
);
@ -141,10 +215,159 @@ impl VerificationMachine {
}
}
pub async fn receive_event(
pub async fn receive_room_event(
&self,
event: &mut AnyToDeviceEvent,
room_id: &RoomId,
event: &AnySyncRoomEvent,
) -> Result<(), CryptoStoreError> {
if let AnySyncRoomEvent::Message(m) = event {
// 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() {
if let AnySyncMessageEvent::KeyVerificationReady(_e) = m {
// TODO if there is a verification request, go into passive
// mode since another device is handling this request.
}
return Ok(());
}
match m {
AnySyncMessageEvent::RoomMessage(m) => {
if let MessageEventContent::VerificationRequest(r) = &m.content {
if self.account.user_id() == &r.to {
info!(
"Received a new verification request from {} {}",
m.sender, r.from_device
);
let request = VerificationRequest::from_request_event(
self.account.clone(),
self.private_identity.lock().await.clone(),
self.store.clone(),
room_id,
&m.sender,
&m.event_id,
r,
);
self.requests.insert(m.event_id.clone(), request);
}
}
}
AnySyncMessageEvent::KeyVerificationReady(e) => {
if let Some(request) = self.requests.get(&e.content.relation.event_id) {
if &e.sender == request.other_user() {
// TODO remove this unwrap.
request.receive_ready(&e.sender, &e.content).unwrap();
}
}
}
AnySyncMessageEvent::KeyVerificationStart(e) => {
info!(
"Received a new verification start event from {} {}",
e.sender, e.content.from_device
);
if let Some((_, request)) = self.requests.remove(&e.content.relation.event_id) {
if let Some(d) = self
.store
.get_device(&e.sender, &e.content.from_device)
.await?
{
match request.into_started_sas(
e,
d,
self.store.get_user_identity(&e.sender).await?,
) {
Ok(s) => {
info!(
"Started a new SAS verification, \
automatically accepting because of in-room"
);
// TODO remove this unwrap
let accept_request = s.accept().unwrap();
self.room_verifications
.insert(e.content.relation.event_id.clone(), s);
self.outgoing_room_messages
.insert(accept_request.request_id(), accept_request.into());
}
Err(c) => {
warn!(
"Can't start key verification with {} {}, canceling: {:?}",
e.sender, e.content.from_device, c
);
// self.queue_up_content(&e.sender, &e.content.from_device, c)
}
}
}
}
}
AnySyncMessageEvent::KeyVerificationKey(e) => {
if let Some(s) = self.room_verifications.get(&e.content.relation.event_id) {
self.receive_room_event_helper(
&s,
&m.clone().into_full_event(room_id.clone()),
)
};
}
AnySyncMessageEvent::KeyVerificationMac(e) => {
if let Some(s) = self.room_verifications.get(&e.content.relation.event_id) {
self.receive_room_event_helper(
&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 => {
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());
}
VerificationResult::SignatureUpload(r) => {
let request: OutgoingRequest = r.into();
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,
);
}
}
}
}
};
}
_ => (),
}
}
Ok(())
}
pub async fn receive_event(&self, event: &AnyToDeviceEvent) -> Result<(), CryptoStoreError> {
trace!("Received a key verification event {:?}", event);
match event {
@ -166,7 +389,7 @@ impl VerificationMachine {
private_identity,
d,
self.store.clone(),
e,
e.content.clone(),
self.store.get_user_identity(&e.sender).await?,
) {
Ok(s) => {
@ -210,9 +433,9 @@ impl VerificationMachine {
VerificationResult::Ok => (),
VerificationResult::Cancel(r) => {
self.outgoing_to_device_messages.insert(
r.txn_id,
r.request_id(),
OutgoingRequest {
request_id: r.txn_id,
request_id: r.request_id(),
request: Arc::new(r.into()),
},
);
@ -248,7 +471,6 @@ mod test {
};
use matrix_sdk_common::{
events::AnyToDeviceEventContent,
identifiers::{DeviceId, UserId},
locks::Mutex,
};
@ -300,10 +522,11 @@ mod test {
bob_store,
None,
);
machine
.receive_event(&mut wrap_any_to_device_content(
.receive_event(&wrap_any_to_device_content(
bob_sas.user_id(),
AnyToDeviceEventContent::KeyVerificationStart(start_content),
start_content.into(),
))
.await
.unwrap();
@ -323,20 +546,20 @@ mod test {
async fn full_flow() {
let (alice_machine, bob) = setup_verification_machine().await;
let alice = alice_machine.get_sas(bob.flow_id()).unwrap();
let alice = alice_machine.get_sas(bob.flow_id().as_str()).unwrap();
let mut event = alice
let event = alice
.accept()
.map(|c| wrap_any_to_device_content(alice.user_id(), get_content_from_request(&c)))
.unwrap();
let mut event = bob
.receive_event(&mut event)
let event = bob
.receive_event(&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();
alice_machine.receive_event(&event).await.unwrap();
assert!(!alice_machine.outgoing_to_device_messages.is_empty());
let request = alice_machine
@ -348,33 +571,34 @@ mod test {
let txn_id = *request.request_id();
let r = if let OutgoingRequests::ToDeviceRequest(r) = request.request() {
r
r.clone()
} else {
panic!("Invalid request type");
};
let mut event = wrap_any_to_device_content(alice.user_id(), get_content_from_request(r));
let event =
wrap_any_to_device_content(alice.user_id(), get_content_from_request(&r.into()));
drop(request);
alice_machine.mark_request_as_sent(&txn_id);
assert!(bob.receive_event(&mut event).is_none());
assert!(bob.receive_event(&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(
let event = wrap_any_to_device_content(
alice.user_id(),
get_content_from_request(&alice.confirm().await.unwrap().0.unwrap()),
);
bob.receive_event(&mut event);
bob.receive_event(&event);
let mut event = wrap_any_to_device_content(
let event = wrap_any_to_device_content(
bob.user_id(),
get_content_from_request(&bob.confirm().await.unwrap().0.unwrap()),
);
alice.receive_event(&mut event);
alice.receive_event(&event);
assert!(alice.is_done());
assert!(bob.is_done());
@ -384,7 +608,7 @@ mod test {
#[tokio::test]
async fn timing_out() {
let (alice_machine, bob) = setup_verification_machine().await;
let alice = alice_machine.get_sas(bob.flow_id()).unwrap();
let alice = alice_machine.get_sas(bob.flow_id().as_str()).unwrap();
assert!(!alice.timed_out());
assert!(alice_machine.outgoing_to_device_messages.is_empty());

View file

@ -13,14 +13,19 @@
// limitations under the License.
mod machine;
mod requests;
mod sas;
pub use machine::VerificationMachine;
pub use requests::VerificationRequest;
pub use sas::{Sas, VerificationResult};
#[cfg(test)]
pub(crate) mod test {
use crate::requests::{OutgoingRequest, OutgoingRequests, ToDeviceRequest};
use crate::{
requests::{OutgoingRequest, OutgoingRequests},
OutgoingVerificationRequest,
};
use serde_json::Value;
use matrix_sdk_common::{
@ -28,7 +33,12 @@ pub(crate) mod test {
identifiers::UserId,
};
pub(crate) fn request_to_event(sender: &UserId, request: &ToDeviceRequest) -> AnyToDeviceEvent {
use super::sas::OutgoingContent;
pub(crate) fn request_to_event(
sender: &UserId,
request: &OutgoingVerificationRequest,
) -> AnyToDeviceEvent {
let content = get_content_from_request(request);
wrap_any_to_device_content(sender, content)
}
@ -38,15 +48,21 @@ pub(crate) mod test {
request: &OutgoingRequest,
) -> AnyToDeviceEvent {
match request.request() {
OutgoingRequests::ToDeviceRequest(r) => request_to_event(sender, r),
OutgoingRequests::ToDeviceRequest(r) => request_to_event(sender, &r.clone().into()),
_ => panic!("Unsupported outgoing request"),
}
}
pub(crate) fn wrap_any_to_device_content(
sender: &UserId,
content: AnyToDeviceEventContent,
content: OutgoingContent,
) -> AnyToDeviceEvent {
let content = if let OutgoingContent::ToDevice(c) = content {
c
} else {
unreachable!()
};
match content {
AnyToDeviceEventContent::KeyVerificationKey(c) => {
AnyToDeviceEvent::KeyVerificationKey(ToDeviceEvent {
@ -77,7 +93,15 @@ pub(crate) mod test {
}
}
pub(crate) fn get_content_from_request(request: &ToDeviceRequest) -> AnyToDeviceEventContent {
pub(crate) fn get_content_from_request(
request: &OutgoingVerificationRequest,
) -> OutgoingContent {
let request = if let OutgoingVerificationRequest::ToDevice(r) = request {
r
} else {
unreachable!()
};
let json: Value = serde_json::from_str(
request
.messages
@ -109,5 +133,6 @@ pub(crate) mod test {
),
_ => unreachable!(),
}
.into()
}
}

View file

@ -0,0 +1,500 @@
// 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.
#![allow(dead_code)]
use std::sync::{Arc, Mutex};
use matrix_sdk_common::{
api::r0::message::send_message_event::Response as RoomMessageResponse,
events::{
key::verification::{
ready::ReadyEventContent, start::StartEventContent, Relation, VerificationMethod,
},
room::message::KeyVerificationRequestEventContent,
MessageEvent, SyncMessageEvent,
},
identifiers::{DeviceId, DeviceIdBox, EventId, RoomId, UserId},
};
use crate::{
olm::{PrivateCrossSigningIdentity, ReadOnlyAccount},
store::CryptoStore,
ReadOnlyDevice, Sas, UserIdentities,
};
use super::sas::OutgoingContent;
const SUPPORTED_METHODS: &[VerificationMethod] = &[VerificationMethod::MSasV1];
#[derive(Clone, Debug)]
/// TODO
pub struct VerificationRequest {
inner: Arc<Mutex<InnerRequest>>,
account: ReadOnlyAccount,
other_user_id: Arc<UserId>,
private_cross_signing_identity: PrivateCrossSigningIdentity,
store: Arc<Box<dyn CryptoStore>>,
room_id: Arc<RoomId>,
}
impl VerificationRequest {
/// TODO
pub fn new(
account: ReadOnlyAccount,
private_cross_signing_identity: PrivateCrossSigningIdentity,
store: Arc<Box<dyn CryptoStore>>,
room_id: Arc<RoomId>,
other_user: &UserId,
) -> Self {
let inner = Mutex::new(InnerRequest::Created(RequestState::new(
account.user_id(),
account.device_id(),
other_user,
)))
.into();
Self {
inner,
account,
private_cross_signing_identity,
store,
other_user_id: other_user.clone().into(),
room_id,
}
}
/// TODO
pub fn request(&self) -> Option<KeyVerificationRequestEventContent> {
match &*self.inner.lock().unwrap() {
InnerRequest::Created(c) => Some(c.as_content()),
_ => None,
}
}
/// The id of the other user that is participating in this verification
/// request.
pub fn other_user(&self) -> &UserId {
&self.other_user_id
}
/// Mark the request as sent.
pub fn mark_as_sent(&self, response: &RoomMessageResponse) {
let mut inner = self.inner.lock().unwrap();
if let InnerRequest::Created(c) = &*inner {
*inner = InnerRequest::Sent(c.clone().into_sent(response));
}
}
pub(crate) fn from_request_event(
account: ReadOnlyAccount,
private_cross_signing_identity: PrivateCrossSigningIdentity,
store: Arc<Box<dyn CryptoStore>>,
room_id: &RoomId,
sender: &UserId,
event_id: &EventId,
content: &KeyVerificationRequestEventContent,
) -> Self {
Self {
inner: Arc::new(Mutex::new(InnerRequest::Requested(
RequestState::from_request_event(
account.user_id(),
account.device_id(),
sender,
event_id,
content,
),
))),
account,
other_user_id: sender.clone().into(),
private_cross_signing_identity,
store,
room_id: room_id.clone().into(),
}
}
/// The room id where the verification is happening.
pub fn room_id(&self) -> &RoomId {
&self.room_id
}
/// Accept the verification request.
pub fn accept(&self) -> Option<ReadyEventContent> {
self.inner.lock().unwrap().accept()
}
pub(crate) fn receive_ready(
&self,
sender: &UserId,
content: &ReadyEventContent,
) -> Result<(), ()> {
let mut inner = self.inner.lock().unwrap();
if let InnerRequest::Sent(s) = &*inner {
*inner = InnerRequest::Ready(s.clone().into_ready(sender, content));
}
Ok(())
}
/// Is the verification request ready to start a verification flow.
pub fn is_ready(&self) -> bool {
matches!(&*self.inner.lock().unwrap(), InnerRequest::Ready(_))
}
pub(crate) fn into_started_sas(
self,
event: &SyncMessageEvent<StartEventContent>,
device: ReadOnlyDevice,
user_identity: Option<UserIdentities>,
) -> Result<Sas, OutgoingContent> {
match &*self.inner.lock().unwrap() {
InnerRequest::Ready(s) => s.clone().into_started_sas(
&event.clone().into_full_event(self.room_id().clone()),
self.store.clone(),
self.account.clone(),
self.private_cross_signing_identity.clone(),
device,
user_identity,
),
// TODO cancel here since we got a missmatched message or do
// nothing?
_ => todo!(),
}
}
}
#[derive(Debug)]
enum InnerRequest {
Created(RequestState<Created>),
Sent(RequestState<Sent>),
Requested(RequestState<Requested>),
Ready(RequestState<Ready>),
Passive(RequestState<Passive>),
}
impl InnerRequest {
fn accept(&mut self) -> Option<ReadyEventContent> {
if let InnerRequest::Requested(s) = self {
let (state, content) = s.clone().accept();
*self = InnerRequest::Ready(state);
Some(content)
} else {
None
}
}
fn into_started_sas(
self,
event: &MessageEvent<StartEventContent>,
store: Arc<Box<dyn CryptoStore>>,
account: ReadOnlyAccount,
private_identity: PrivateCrossSigningIdentity,
other_device: ReadOnlyDevice,
other_identity: Option<UserIdentities>,
) -> Result<Option<Sas>, OutgoingContent> {
if let InnerRequest::Ready(s) = self {
Ok(Some(s.into_started_sas(
event,
store,
account,
private_identity,
other_device,
other_identity,
)?))
} else {
Ok(None)
}
}
}
#[derive(Clone, Debug)]
struct RequestState<S: Clone> {
/// Our own user id.
pub own_user_id: UserId,
/// Our own device id.
pub own_device_id: DeviceIdBox,
/// The id of the user which is participating in this verification request.
pub other_user_id: UserId,
/// The verification request state we are in.
state: S,
}
#[derive(Clone, Debug)]
struct Created {}
impl RequestState<Created> {
fn new(own_user_id: &UserId, own_device_id: &DeviceId, other_user: &UserId) -> Self {
Self {
own_user_id: own_user_id.clone(),
own_device_id: own_device_id.into(),
other_user_id: other_user.clone(),
state: Created {},
}
}
fn as_content(&self) -> KeyVerificationRequestEventContent {
KeyVerificationRequestEventContent {
body: format!(
"{} is requesting to verify your key, but your client does not \
support in-chat key verification. You will need to use legacy \
key verification to verify keys.",
self.own_user_id
),
methods: SUPPORTED_METHODS.to_vec(),
from_device: self.own_device_id.clone(),
to: self.other_user_id.clone(),
}
}
fn into_sent(self, response: &RoomMessageResponse) -> RequestState<Sent> {
RequestState {
own_user_id: self.own_user_id,
own_device_id: self.own_device_id,
other_user_id: self.other_user_id,
state: Sent {
methods: SUPPORTED_METHODS.to_vec(),
flow_id: response.event_id.clone(),
},
}
}
}
#[derive(Clone, Debug)]
struct Sent {
/// The verification methods supported by the sender.
pub methods: Vec<VerificationMethod>,
/// The event id of our `m.key.verification.request` event which acts as an
/// unique id identifying this verification flow.
pub flow_id: EventId,
}
impl RequestState<Sent> {
fn into_ready(self, _sender: &UserId, content: &ReadyEventContent) -> RequestState<Ready> {
// TODO check the flow id, and that the methods match what we suggested.
RequestState {
own_user_id: self.own_user_id,
own_device_id: self.own_device_id,
other_user_id: self.other_user_id,
state: Ready {
methods: content.methods.to_owned(),
other_device_id: content.from_device.clone(),
flow_id: self.state.flow_id,
},
}
}
}
#[derive(Clone, Debug)]
struct Requested {
/// The verification methods supported by the sender.
pub methods: Vec<VerificationMethod>,
/// The event id of the `m.key.verification.request` event which acts as an
/// unique id identifying this verification flow.
pub flow_id: EventId,
/// The device id of the device that responded to the verification request.
pub other_device_id: DeviceIdBox,
}
impl RequestState<Requested> {
fn from_request_event(
own_user_id: &UserId,
own_device_id: &DeviceId,
sender: &UserId,
event_id: &EventId,
content: &KeyVerificationRequestEventContent,
) -> RequestState<Requested> {
// TODO only create this if we suport the methods
RequestState {
own_user_id: own_user_id.clone(),
own_device_id: own_device_id.into(),
other_user_id: sender.clone(),
state: Requested {
methods: content.methods.clone(),
flow_id: event_id.clone(),
other_device_id: content.from_device.clone(),
},
}
}
fn accept(self) -> (RequestState<Ready>, ReadyEventContent) {
let state = RequestState {
own_user_id: self.own_user_id,
own_device_id: self.own_device_id.clone(),
other_user_id: self.other_user_id,
state: Ready {
methods: self.state.methods.clone(),
other_device_id: self.state.other_device_id.clone(),
flow_id: self.state.flow_id.clone(),
},
};
let content = ReadyEventContent {
from_device: self.own_device_id,
methods: self.state.methods,
relation: Relation {
event_id: self.state.flow_id,
},
};
(state, content)
}
}
#[derive(Clone, Debug)]
struct Ready {
/// The verification methods supported by the sender.
pub methods: Vec<VerificationMethod>,
/// The device id of the device that responded to the verification request.
pub other_device_id: DeviceIdBox,
/// The event id of the `m.key.verification.request` event which acts as an
/// unique id identifying this verification flow.
pub flow_id: EventId,
}
impl RequestState<Ready> {
fn into_started_sas(
self,
event: &MessageEvent<StartEventContent>,
store: Arc<Box<dyn CryptoStore>>,
account: ReadOnlyAccount,
private_identity: PrivateCrossSigningIdentity,
other_device: ReadOnlyDevice,
other_identity: Option<UserIdentities>,
) -> Result<Sas, OutgoingContent> {
Sas::from_start_event(
account,
private_identity,
other_device,
store,
(event.room_id.clone(), event.content.clone()),
other_identity,
)
}
fn start_sas(
self,
_store: Arc<Box<dyn CryptoStore>>,
_account: ReadOnlyAccount,
_private_identity: PrivateCrossSigningIdentity,
_other_device: ReadOnlyDevice,
_other_identity: Option<UserIdentities>,
) -> (Sas, OutgoingContent) {
todo!()
// Sas::start_in_room(
// account,
// private_identity,
// other_device,
// store,
// other_identity,
// )
}
}
#[derive(Clone, Debug)]
struct Passive {
/// The device id of the device that responded to the verification request.
pub other_device_id: DeviceIdBox,
/// The event id of the `m.key.verification.request` event which acts as an
/// unique id identifying this verification flow.
pub flow_id: EventId,
}
#[cfg(test)]
mod test {
use std::convert::TryFrom;
use matrix_sdk_common::{
api::r0::message::send_message_event::Response as RoomMessageResponse,
identifiers::{event_id, room_id, DeviceIdBox, UserId},
};
use matrix_sdk_test::async_test;
use crate::{
olm::{PrivateCrossSigningIdentity, ReadOnlyAccount},
store::{CryptoStore, MemoryStore},
};
use super::VerificationRequest;
fn alice_id() -> UserId {
UserId::try_from("@alice:example.org").unwrap()
}
fn alice_device_id() -> DeviceIdBox {
"JLAFKJWSCS".into()
}
fn bob_id() -> UserId {
UserId::try_from("@bob:example.org").unwrap()
}
fn bob_device_id() -> DeviceIdBox {
"BOBDEVCIE".into()
}
#[async_test]
async fn test_request_accepting() {
let event_id = event_id!("$1234localhost");
let room_id = room_id!("!test:localhost");
let alice = ReadOnlyAccount::new(&alice_id(), &alice_device_id());
let alice_store: Box<dyn CryptoStore> = Box::new(MemoryStore::new());
let alice_identity = PrivateCrossSigningIdentity::empty(alice_id());
let bob = ReadOnlyAccount::new(&bob_id(), &bob_device_id());
let bob_store: Box<dyn CryptoStore> = Box::new(MemoryStore::new());
let bob_identity = PrivateCrossSigningIdentity::empty(alice_id());
let bob_request = VerificationRequest::new(
bob,
bob_identity,
bob_store.into(),
room_id.clone().into(),
&alice_id(),
);
let content = bob_request.request().unwrap();
let alice_request = VerificationRequest::from_request_event(
alice,
alice_identity,
alice_store.into(),
&room_id,
&bob_id(),
&event_id,
&content,
);
let content = alice_request.accept().unwrap();
let response = RoomMessageResponse::new(event_id);
bob_request.mark_as_sent(&response);
bob_request.receive_ready(&alice_id(), &content).unwrap();
assert!(bob_request.is_ready());
assert!(alice_request.is_ready());
}
}

View file

@ -0,0 +1,278 @@
// 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.
#![allow(dead_code)]
use std::{collections::BTreeMap, convert::TryInto};
use matrix_sdk_common::{
events::{
key::verification::{
accept::{AcceptEventContent, AcceptMethod, AcceptToDeviceEventContent},
cancel::{CancelEventContent, CancelToDeviceEventContent},
done::DoneEventContent,
key::{KeyEventContent, KeyToDeviceEventContent},
mac::{MacEventContent, MacToDeviceEventContent},
start::{StartEventContent, StartMethod, StartToDeviceEventContent},
},
AnyMessageEventContent, AnyToDeviceEventContent,
},
identifiers::RoomId,
CanonicalJsonValue,
};
use super::FlowId;
#[derive(Clone, Debug)]
pub enum StartContent {
ToDevice(StartToDeviceEventContent),
Room(RoomId, StartEventContent),
}
impl StartContent {
pub fn method(&self) -> &StartMethod {
match self {
StartContent::ToDevice(c) => &c.method,
StartContent::Room(_, c) => &c.method,
}
}
pub fn flow_id(&self) -> FlowId {
match self {
StartContent::ToDevice(c) => FlowId::ToDevice(c.transaction_id.clone()),
StartContent::Room(r, c) => FlowId::InRoom(r.clone(), c.relation.event_id.clone()),
}
}
pub fn canonical_json(self) -> CanonicalJsonValue {
let content = match self {
StartContent::ToDevice(c) => serde_json::to_value(c),
StartContent::Room(_, c) => serde_json::to_value(c),
};
content
.expect("Can't serialize content")
.try_into()
.expect("Can't canonicalize content")
}
}
impl From<(RoomId, StartEventContent)> for StartContent {
fn from(tuple: (RoomId, StartEventContent)) -> Self {
StartContent::Room(tuple.0, tuple.1)
}
}
impl From<StartToDeviceEventContent> for StartContent {
fn from(content: StartToDeviceEventContent) -> Self {
StartContent::ToDevice(content)
}
}
#[derive(Clone, Debug)]
pub enum AcceptContent {
ToDevice(AcceptToDeviceEventContent),
Room(RoomId, AcceptEventContent),
}
impl AcceptContent {
pub fn flow_id(&self) -> FlowId {
match self {
AcceptContent::ToDevice(c) => FlowId::ToDevice(c.transaction_id.clone()),
AcceptContent::Room(r, c) => FlowId::InRoom(r.clone(), c.relation.event_id.clone()),
}
}
pub fn method(&self) -> &AcceptMethod {
match self {
AcceptContent::ToDevice(c) => &c.method,
AcceptContent::Room(_, c) => &c.method,
}
}
}
impl From<AcceptToDeviceEventContent> for AcceptContent {
fn from(content: AcceptToDeviceEventContent) -> Self {
AcceptContent::ToDevice(content)
}
}
impl From<(RoomId, AcceptEventContent)> for AcceptContent {
fn from(content: (RoomId, AcceptEventContent)) -> Self {
AcceptContent::Room(content.0, content.1)
}
}
pub enum KeyContent {
ToDevice(KeyToDeviceEventContent),
Room(RoomId, KeyEventContent),
}
impl KeyContent {
pub fn flow_id(&self) -> FlowId {
match self {
KeyContent::ToDevice(c) => FlowId::ToDevice(c.transaction_id.clone()),
KeyContent::Room(r, c) => FlowId::InRoom(r.clone(), c.relation.event_id.clone()),
}
}
pub fn public_key(&self) -> &str {
match self {
KeyContent::ToDevice(c) => &c.key,
KeyContent::Room(_, c) => &c.key,
}
}
}
impl From<KeyToDeviceEventContent> for KeyContent {
fn from(content: KeyToDeviceEventContent) -> Self {
KeyContent::ToDevice(content)
}
}
impl From<(RoomId, KeyEventContent)> for KeyContent {
fn from(content: (RoomId, KeyEventContent)) -> Self {
KeyContent::Room(content.0, content.1)
}
}
pub enum MacContent {
ToDevice(MacToDeviceEventContent),
Room(RoomId, MacEventContent),
}
impl MacContent {
pub fn flow_id(&self) -> FlowId {
match self {
MacContent::ToDevice(c) => FlowId::ToDevice(c.transaction_id.clone()),
MacContent::Room(r, c) => FlowId::InRoom(r.clone(), c.relation.event_id.clone()),
}
}
pub fn mac(&self) -> &BTreeMap<String, String> {
match self {
MacContent::ToDevice(c) => &c.mac,
MacContent::Room(_, c) => &c.mac,
}
}
pub fn keys(&self) -> &str {
match self {
MacContent::ToDevice(c) => &c.keys,
MacContent::Room(_, c) => &c.keys,
}
}
}
impl From<MacToDeviceEventContent> for MacContent {
fn from(content: MacToDeviceEventContent) -> Self {
MacContent::ToDevice(content)
}
}
impl From<(RoomId, MacEventContent)> for MacContent {
fn from(content: (RoomId, MacEventContent)) -> Self {
MacContent::Room(content.0, content.1)
}
}
pub enum CancelContent {
ToDevice(CancelToDeviceEventContent),
Room(RoomId, CancelEventContent),
}
impl From<(RoomId, CancelEventContent)> for CancelContent {
fn from(content: (RoomId, CancelEventContent)) -> Self {
CancelContent::Room(content.0, content.1)
}
}
impl From<CancelToDeviceEventContent> for CancelContent {
fn from(content: CancelToDeviceEventContent) -> Self {
CancelContent::ToDevice(content)
}
}
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),
ToDevice(AnyToDeviceEventContent),
}
impl From<StartContent> for OutgoingContent {
fn from(content: StartContent) -> Self {
match content {
StartContent::Room(r, c) => (r, AnyMessageEventContent::KeyVerificationStart(c)).into(),
StartContent::ToDevice(c) => AnyToDeviceEventContent::KeyVerificationStart(c).into(),
}
}
}
impl From<CancelContent> for OutgoingContent {
fn from(content: CancelContent) -> Self {
match content {
CancelContent::Room(r, c) => {
(r, AnyMessageEventContent::KeyVerificationCancel(c)).into()
}
CancelContent::ToDevice(c) => AnyToDeviceEventContent::KeyVerificationCancel(c).into(),
}
}
}
impl From<KeyContent> for OutgoingContent {
fn from(content: KeyContent) -> Self {
match content {
KeyContent::Room(r, c) => (r, AnyMessageEventContent::KeyVerificationKey(c)).into(),
KeyContent::ToDevice(c) => AnyToDeviceEventContent::KeyVerificationKey(c).into(),
}
}
}
impl From<DoneContent> for OutgoingContent {
fn from(content: DoneContent) -> Self {
match content {
DoneContent::Room(r, c) => (r, AnyMessageEventContent::KeyVerificationDone(c)).into(),
}
}
}
impl From<AnyToDeviceEventContent> for OutgoingContent {
fn from(content: AnyToDeviceEventContent) -> Self {
OutgoingContent::ToDevice(content)
}
}
impl From<(RoomId, AnyMessageEventContent)> for OutgoingContent {
fn from(content: (RoomId, AnyMessageEventContent)) -> Self {
OutgoingContent::Room(content.0, content.1)
}
}

View file

@ -23,13 +23,14 @@ use matrix_sdk_common::{
api::r0::to_device::DeviceIdOrAllDevices,
events::{
key::verification::{
cancel::CancelCode, mac::MacToDeviceEventContent, start::StartToDeviceEventContent,
cancel::CancelCode,
mac::{MacEventContent, MacToDeviceEventContent},
Relation,
},
AnyToDeviceEventContent, EventType, ToDeviceEvent,
AnyToDeviceEventContent, EventType,
},
identifiers::{DeviceId, DeviceKeyAlgorithm, DeviceKeyId, UserId},
uuid::Uuid,
CanonicalJsonValue,
};
use crate::{
@ -38,6 +39,11 @@ use crate::{
ReadOnlyAccount, ToDeviceRequest,
};
use super::{
event_enums::{MacContent, StartContent},
sas_state::FlowId,
};
#[derive(Clone, Debug)]
pub struct SasIds {
pub account: ReadOnlyAccount,
@ -55,15 +61,12 @@ pub struct SasIds {
///
/// * `content` - The `m.key.verification.start` event content that started the
/// interactive verification process.
pub fn calculate_commitment(public_key: &str, content: &StartToDeviceEventContent) -> String {
let json_content: CanonicalJsonValue = serde_json::to_value(content)
.expect("Can't serialize content")
.try_into()
.expect("Can't canonicalize content");
pub fn calculate_commitment(public_key: &str, content: impl Into<StartContent>) -> String {
let content = content.into().canonical_json();
encode(
Sha256::new()
.chain(&format!("{}{}", public_key, json_content))
.chain(&format!("{}{}", public_key, content))
.finalize(),
)
}
@ -187,7 +190,8 @@ pub fn receive_mac_event(
sas: &OlmSas,
ids: &SasIds,
flow_id: &str,
event: &ToDeviceEvent<MacToDeviceEventContent>,
sender: &UserId,
content: &MacContent,
) -> Result<(Vec<ReadOnlyDevice>, Vec<UserIdentities>), CancelCode> {
let mut verified_devices = Vec::new();
let mut verified_identities = Vec::new();
@ -196,25 +200,25 @@ pub fn receive_mac_event(
trace!(
"Received a key.verification.mac event from {} {}",
event.sender,
sender,
ids.other_device.device_id()
);
let mut keys = event.content.mac.keys().cloned().collect::<Vec<String>>();
let mut keys = content.mac().keys().cloned().collect::<Vec<String>>();
keys.sort();
let keys = sas
.calculate_mac(&keys.join(","), &format!("{}KEY_IDS", &info))
.expect("Can't calculate SAS MAC");
if keys != event.content.keys {
if keys != content.keys() {
return Err(CancelCode::KeyMismatch);
}
for (key_id, key_mac) in &event.content.mac {
for (key_id, key_mac) in content.mac() {
trace!(
"Checking MAC for the key id {} from {} {}",
key_id,
event.sender,
sender,
ids.other_device.device_id()
);
let key_id: DeviceKeyId = match key_id.as_str().try_into() {
@ -228,6 +232,12 @@ pub fn receive_mac_event(
.calculate_mac(key, &format!("{}{}", info, key_id))
.expect("Can't calculate SAS MAC")
{
trace!(
"Successfully verified the device key {} from {}",
key_id,
sender
);
verified_devices.push(ids.other_device.clone());
} else {
return Err(CancelCode::KeyMismatch);
@ -244,7 +254,7 @@ pub fn receive_mac_event(
trace!(
"Successfully verified the master key {} from {}",
key_id,
event.sender
sender
);
verified_identities.push(identity.clone())
} else {
@ -256,7 +266,7 @@ pub fn receive_mac_event(
"Key ID {} in MAC event from {} {} doesn't belong to any device \
or user identity",
key_id,
event.sender,
sender,
ids.other_device.device_id()
);
}
@ -298,12 +308,12 @@ fn extra_mac_info_send(ids: &SasIds, flow_id: &str) -> String {
/// # Panics
///
/// This will panic if the public key of the other side wasn't set.
pub fn get_mac_content(sas: &OlmSas, ids: &SasIds, flow_id: &str) -> MacToDeviceEventContent {
pub fn get_mac_content(sas: &OlmSas, ids: &SasIds, flow_id: &FlowId) -> MacContent {
let mut mac: BTreeMap<String, String> = BTreeMap::new();
let key_id = DeviceKeyId::from_parts(DeviceKeyAlgorithm::Ed25519, ids.account.device_id());
let key = ids.account.identity_keys().ed25519();
let info = extra_mac_info_send(ids, flow_id);
let info = extra_mac_info_send(ids, flow_id.as_str());
mac.insert(
key_id.to_string(),
@ -319,10 +329,24 @@ pub fn get_mac_content(sas: &OlmSas, ids: &SasIds, flow_id: &str) -> MacToDevice
.calculate_mac(&keys.join(","), &format!("{}KEY_IDS", &info))
.expect("Can't calculate SAS MAC");
MacToDeviceEventContent {
transaction_id: flow_id.to_owned(),
keys,
mac,
match flow_id {
FlowId::ToDevice(s) => MacToDeviceEventContent {
transaction_id: s.to_string(),
keys,
mac,
}
.into(),
FlowId::InRoom(r, e) => (
r.clone(),
MacEventContent {
mac,
keys,
relation: Relation {
event_id: e.clone(),
},
},
)
.into(),
}
}
@ -546,7 +570,7 @@ mod test {
});
let content: StartToDeviceEventContent = serde_json::from_value(content).unwrap();
let calculated_commitment = calculate_commitment(public_key, &content);
let calculated_commitment = calculate_commitment(public_key, content);
assert_eq!(commitment, &calculated_commitment);
}

View file

@ -0,0 +1,381 @@
// 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.
#[cfg(test)]
use std::time::Instant;
use std::sync::Arc;
use matrix_sdk_common::{
events::{key::verification::cancel::CancelCode, AnyMessageEvent, AnyToDeviceEvent},
identifiers::{EventId, RoomId},
};
use crate::{
identities::{ReadOnlyDevice, UserIdentities},
ReadOnlyAccount,
};
use super::{
event_enums::{AcceptContent, CancelContent, MacContent, OutgoingContent},
sas_state::{
Accepted, Canceled, Confirmed, Created, Done, FlowId, KeyReceived, MacReceived, SasState,
Started, WaitingForDone,
},
StartContent,
};
#[derive(Clone, Debug)]
pub enum InnerSas {
Created(SasState<Created>),
Started(SasState<Started>),
Accepted(SasState<Accepted>),
KeyRecieved(SasState<KeyReceived>),
Confirmed(SasState<Confirmed>),
MacReceived(SasState<MacReceived>),
WaitingForDone(SasState<WaitingForDone>),
WaitingForDoneUnconfirmed(SasState<WaitingForDone>),
Done(SasState<Done>),
Canceled(SasState<Canceled>),
}
impl InnerSas {
pub fn start(
account: ReadOnlyAccount,
other_device: ReadOnlyDevice,
other_identity: Option<UserIdentities>,
) -> (InnerSas, StartContent) {
let sas = SasState::<Created>::new(account, other_device, other_identity);
let content = sas.as_content();
(InnerSas::Created(sas), content)
}
pub fn start_in_room(
event_id: EventId,
room_id: RoomId,
account: ReadOnlyAccount,
other_device: ReadOnlyDevice,
other_identity: Option<UserIdentities>,
) -> (InnerSas, StartContent) {
let sas = SasState::<Created>::new_in_room(
room_id,
event_id,
account,
other_device,
other_identity,
);
let content = sas.as_content();
(InnerSas::Created(sas), content)
}
pub fn from_start_event(
account: ReadOnlyAccount,
other_device: ReadOnlyDevice,
content: impl Into<StartContent>,
other_identity: Option<UserIdentities>,
) -> Result<InnerSas, CancelContent> {
match SasState::<Started>::from_start_event(account, other_device, other_identity, content)
{
Ok(s) => Ok(InnerSas::Started(s)),
Err(s) => Err(s.as_content()),
}
}
pub fn accept(&self) -> Option<AcceptContent> {
if let InnerSas::Started(s) = self {
Some(s.as_content())
} else {
None
}
}
#[cfg(test)]
#[allow(dead_code)]
pub fn set_creation_time(&mut self, time: Instant) {
match self {
InnerSas::Created(s) => s.set_creation_time(time),
InnerSas::Started(s) => s.set_creation_time(time),
InnerSas::Canceled(s) => s.set_creation_time(time),
InnerSas::Accepted(s) => s.set_creation_time(time),
InnerSas::KeyRecieved(s) => s.set_creation_time(time),
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),
}
}
pub fn cancel(self, code: CancelCode) -> (InnerSas, Option<CancelContent>) {
let sas = match self {
InnerSas::Created(s) => s.cancel(code),
InnerSas::Started(s) => s.cancel(code),
InnerSas::Accepted(s) => s.cancel(code),
InnerSas::KeyRecieved(s) => s.cancel(code),
InnerSas::MacReceived(s) => s.cancel(code),
_ => return (self, None),
};
let content = sas.as_content();
(InnerSas::Canceled(sas), Some(content))
}
pub fn confirm(self) -> (InnerSas, Option<MacContent>) {
match self {
InnerSas::KeyRecieved(s) => {
let sas = s.confirm();
let content = sas.as_content();
(InnerSas::Confirmed(sas), Some(content))
}
InnerSas::MacReceived(s) => {
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),
}
}
#[allow(dead_code)]
pub fn receive_room_event(
self,
event: &AnyMessageEvent,
) -> (InnerSas, Option<OutgoingContent>) {
match event {
AnyMessageEvent::KeyVerificationKey(e) => match self {
InnerSas::Accepted(s) => {
match s.into_key_received(&e.sender, (e.room_id.clone(), e.content.clone())) {
Ok(s) => (InnerSas::KeyRecieved(s), None),
Err(s) => {
let content = s.as_content();
(InnerSas::Canceled(s), Some(content.into()))
}
}
}
InnerSas::Started(s) => {
match s.into_key_received(&e.sender, (e.room_id.clone(), e.content.clone())) {
Ok(s) => {
let content = s.as_content();
(InnerSas::KeyRecieved(s), Some(content.into()))
}
Err(s) => {
let content = s.as_content();
(InnerSas::Canceled(s), Some(content.into()))
}
}
}
_ => (self, None),
},
AnyMessageEvent::KeyVerificationMac(e) => match self {
InnerSas::KeyRecieved(s) => {
match s.into_mac_received(&e.sender, (e.room_id.clone(), e.content.clone())) {
Ok(s) => (InnerSas::MacReceived(s), None),
Err(s) => {
let content = s.as_content();
(InnerSas::Canceled(s), Some(content.into()))
}
}
}
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) => {
let content = s.as_content();
(InnerSas::Canceled(s), Some(content.into()))
}
}
}
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),
}
}
pub fn receive_event(self, event: &AnyToDeviceEvent) -> (InnerSas, Option<OutgoingContent>) {
match event {
AnyToDeviceEvent::KeyVerificationAccept(e) => {
if let InnerSas::Created(s) = self {
match s.into_accepted(&e.sender, e.content.clone()) {
Ok(s) => {
let content = s.as_content();
(InnerSas::Accepted(s), Some(content.into()))
}
Err(s) => {
let content = s.as_content();
(InnerSas::Canceled(s), Some(content.into()))
}
}
} else {
(self, None)
}
}
AnyToDeviceEvent::KeyVerificationKey(e) => match self {
InnerSas::Accepted(s) => match s.into_key_received(&e.sender, e.content.clone()) {
Ok(s) => (InnerSas::KeyRecieved(s), None),
Err(s) => {
let content = s.as_content();
(InnerSas::Canceled(s), Some(content.into()))
}
},
InnerSas::Started(s) => match s.into_key_received(&e.sender, e.content.clone()) {
Ok(s) => {
let content = s.as_content();
(InnerSas::KeyRecieved(s), Some(content.into()))
}
Err(s) => {
let content = s.as_content();
(InnerSas::Canceled(s), Some(content.into()))
}
},
_ => (self, None),
},
AnyToDeviceEvent::KeyVerificationMac(e) => match self {
InnerSas::KeyRecieved(s) => match s.into_mac_received(&e.sender, e.content.clone())
{
Ok(s) => (InnerSas::MacReceived(s), None),
Err(s) => {
let content = s.as_content();
(InnerSas::Canceled(s), Some(content.into()))
}
},
InnerSas::Confirmed(s) => match s.into_done(&e.sender, e.content.clone()) {
Ok(s) => (InnerSas::Done(s), None),
Err(s) => {
let content = s.as_content();
(InnerSas::Canceled(s), Some(content.into()))
}
},
_ => (self, None),
},
_ => (self, None),
}
}
pub fn can_be_presented(&self) -> bool {
match self {
InnerSas::KeyRecieved(_) => true,
InnerSas::MacReceived(_) => true,
_ => false,
}
}
pub fn is_done(&self) -> bool {
matches!(self, InnerSas::Done(_))
}
pub fn is_canceled(&self) -> bool {
matches!(self, InnerSas::Canceled(_))
}
pub fn timed_out(&self) -> bool {
match self {
InnerSas::Created(s) => s.timed_out(),
InnerSas::Started(s) => s.timed_out(),
InnerSas::Canceled(s) => s.timed_out(),
InnerSas::Accepted(s) => s.timed_out(),
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(),
}
}
pub fn verification_flow_id(&self) -> Arc<FlowId> {
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::WaitingForDone(s) => s.verification_flow_id.clone(),
InnerSas::WaitingForDoneUnconfirmed(s) => s.verification_flow_id.clone(),
InnerSas::Done(s) => s.verification_flow_id.clone(),
}
}
pub fn emoji(&self) -> Option<Vec<(&'static str, &'static str)>> {
match self {
InnerSas::KeyRecieved(s) => Some(s.get_emoji()),
InnerSas::MacReceived(s) => Some(s.get_emoji()),
_ => None,
}
}
pub fn decimals(&self) -> Option<(u16, u16, u16)> {
match self {
InnerSas::KeyRecieved(s) => Some(s.get_decimal()),
InnerSas::MacReceived(s) => Some(s.get_decimal()),
_ => None,
}
}
pub fn verified_devices(&self) -> Option<Arc<[ReadOnlyDevice]>> {
if let InnerSas::Done(s) = self {
Some(s.verified_devices())
} else {
None
}
}
pub fn verified_identities(&self) -> Option<Arc<[UserIdentities]>> {
if let InnerSas::Done(s) = self {
Some(s.verified_identities())
} else {
None
}
}
}

View file

@ -12,47 +12,53 @@
// See the License for the specific language governing permissions and
// limitations under the License.
mod event_enums;
mod helpers;
mod inner_sas;
mod sas_state;
#[cfg(test)]
use std::time::Instant;
use event_enums::AcceptContent;
use std::sync::{Arc, Mutex};
use tracing::{error, info, trace, warn};
use matrix_sdk_common::{
api::r0::keys::upload_signatures::Request as SignatureUploadRequest,
events::{
key::verification::{
accept::AcceptToDeviceEventContent, cancel::CancelCode, mac::MacToDeviceEventContent,
start::StartToDeviceEventContent,
},
AnyToDeviceEvent, AnyToDeviceEventContent, ToDeviceEvent,
key::verification::cancel::CancelCode, AnyMessageEvent, AnyMessageEventContent,
AnyToDeviceEvent, AnyToDeviceEventContent,
},
identifiers::{DeviceId, UserId},
identifiers::{DeviceId, EventId, RoomId, UserId},
uuid::Uuid,
};
use crate::{
error::SignatureError,
identities::{LocalTrust, ReadOnlyDevice, UserIdentities},
olm::PrivateCrossSigningIdentity,
requests::{OutgoingVerificationRequest, RoomMessageRequest},
store::{Changes, CryptoStore, CryptoStoreError, DeviceChanges},
ReadOnlyAccount, ToDeviceRequest,
};
pub use helpers::content_to_request;
use sas_state::{
Accepted, Canceled, Confirmed, Created, Done, KeyReceived, MacReceived, SasState, Started,
};
use inner_sas::InnerSas;
pub use sas_state::FlowId;
pub use event_enums::{OutgoingContent, StartContent};
use self::event_enums::CancelContent;
#[derive(Debug)]
/// A result of a verification flow.
#[allow(clippy::large_enum_variant)]
pub enum VerificationResult {
/// The verification succeeded, nothing needs to be done.
Ok,
/// The verification was canceled.
Cancel(ToDeviceRequest),
Cancel(OutgoingVerificationRequest),
/// The verification is done and has signatures that need to be uploaded.
SignatureUpload(SignatureUploadRequest),
}
@ -66,7 +72,7 @@ pub struct Sas {
private_identity: PrivateCrossSigningIdentity,
other_device: ReadOnlyDevice,
other_identity: Option<UserIdentities>,
flow_id: Arc<str>,
flow_id: Arc<FlowId>,
}
impl Sas {
@ -96,7 +102,7 @@ impl Sas {
}
/// Get the unique ID that identifies this SAS verification flow.
pub fn flow_id(&self) -> &str {
pub fn flow_id(&self) -> &FlowId {
&self.flow_id
}
@ -106,6 +112,27 @@ impl Sas {
self.inner.lock().unwrap().set_creation_time(time)
}
fn start_helper(
inner_sas: InnerSas,
account: ReadOnlyAccount,
private_identity: PrivateCrossSigningIdentity,
other_device: ReadOnlyDevice,
store: Arc<Box<dyn CryptoStore>>,
other_identity: Option<UserIdentities>,
) -> Sas {
let flow_id = inner_sas.verification_flow_id();
Sas {
inner: Arc::new(Mutex::new(inner_sas)),
account,
private_identity,
store,
other_device,
flow_id,
other_identity,
}
}
/// Start a new SAS auth flow with the given device.
///
/// # Arguments
@ -122,25 +149,65 @@ impl Sas {
other_device: ReadOnlyDevice,
store: Arc<Box<dyn CryptoStore>>,
other_identity: Option<UserIdentities>,
) -> (Sas, StartToDeviceEventContent) {
) -> (Sas, StartContent) {
let (inner, content) = InnerSas::start(
account.clone(),
other_device.clone(),
other_identity.clone(),
);
let flow_id = inner.verification_flow_id();
let sas = Sas {
inner: Arc::new(Mutex::new(inner)),
account,
private_identity,
store,
other_device,
(
Self::start_helper(
inner,
account,
private_identity,
other_device,
store,
other_identity,
),
content,
)
}
/// Start a new SAS auth flow with the given device inside the given room.
///
/// # Arguments
///
/// * `account` - Our own account.
///
/// * `other_device` - The other device which we are going to verify.
///
/// Returns the new `Sas` object and a `StartEventContent` that needs to be
/// sent out through the server to the other device.
#[allow(dead_code)]
pub(crate) fn start_in_room(
flow_id: EventId,
room_id: RoomId,
account: ReadOnlyAccount,
private_identity: PrivateCrossSigningIdentity,
other_device: ReadOnlyDevice,
store: Arc<Box<dyn CryptoStore>>,
other_identity: Option<UserIdentities>,
) -> (Sas, StartContent) {
let (inner, content) = InnerSas::start_in_room(
flow_id,
other_identity,
};
room_id,
account.clone(),
other_device.clone(),
other_identity.clone(),
);
(sas, content)
(
Self::start_helper(
inner,
account,
private_identity,
other_device,
store,
other_identity,
),
content,
)
}
/// Create a new Sas object from a m.key.verification.start request.
@ -158,15 +225,16 @@ impl Sas {
private_identity: PrivateCrossSigningIdentity,
other_device: ReadOnlyDevice,
store: Arc<Box<dyn CryptoStore>>,
event: &ToDeviceEvent<StartToDeviceEventContent>,
content: impl Into<StartContent>,
other_identity: Option<UserIdentities>,
) -> Result<Sas, AnyToDeviceEventContent> {
) -> Result<Sas, OutgoingContent> {
let inner = InnerSas::from_start_event(
account.clone(),
other_device.clone(),
event,
content,
other_identity.clone(),
)?;
let flow_id = inner.verification_flow_id();
Ok(Sas {
@ -184,10 +252,18 @@ impl Sas {
///
/// This does nothing if the verification was already accepted, otherwise it
/// returns an `AcceptEventContent` that needs to be sent out.
pub fn accept(&self) -> Option<ToDeviceRequest> {
self.inner.lock().unwrap().accept().map(|c| {
let content = AnyToDeviceEventContent::KeyVerificationAccept(c);
self.content_to_request(content)
pub fn accept(&self) -> Option<OutgoingVerificationRequest> {
self.inner.lock().unwrap().accept().map(|c| match c {
AcceptContent::ToDevice(c) => {
let content = AnyToDeviceEventContent::KeyVerificationAccept(c);
self.content_to_request(content).into()
}
AcceptContent::Room(room_id, content) => RoomMessageRequest {
room_id,
txn_id: Uuid::new_v4(),
content: AnyMessageEventContent::KeyVerificationAccept(content),
}
.into(),
})
}
@ -200,7 +276,13 @@ impl Sas {
/// the server.
pub async fn confirm(
&self,
) -> Result<(Option<ToDeviceRequest>, Option<SignatureUploadRequest>), CryptoStoreError> {
) -> Result<
(
Option<OutgoingVerificationRequest>,
Option<SignatureUploadRequest>,
),
CryptoStoreError,
> {
let (content, done) = {
let mut guard = self.inner.lock().unwrap();
let sas: InnerSas = (*guard).clone();
@ -210,8 +292,17 @@ impl Sas {
(content, guard.is_done())
};
let mac_request = content
.map(|c| self.content_to_request(AnyToDeviceEventContent::KeyVerificationMac(c)));
let mac_request = content.map(|c| match c {
event_enums::MacContent::ToDevice(c) => self
.content_to_request(AnyToDeviceEventContent::KeyVerificationMac(c))
.into(),
event_enums::MacContent::Room(r, c) => RoomMessageRequest {
room_id: r,
txn_id: Uuid::new_v4(),
content: AnyMessageEventContent::KeyVerificationMac(c),
}
.into(),
});
if done {
match self.mark_as_done().await? {
@ -452,16 +543,26 @@ impl Sas {
///
/// Returns None if the `Sas` object is already in a canceled state,
/// otherwise it returns a request that needs to be sent out.
pub fn cancel(&self) -> Option<ToDeviceRequest> {
pub fn cancel(&self) -> Option<OutgoingVerificationRequest> {
let mut guard = self.inner.lock().unwrap();
let sas: InnerSas = (*guard).clone();
let (sas, content) = sas.cancel(CancelCode::User);
*guard = sas;
content.map(|c| self.content_to_request(c))
content.map(|c| match c {
CancelContent::Room(room_id, content) => RoomMessageRequest {
room_id,
txn_id: Uuid::new_v4(),
content: AnyMessageEventContent::KeyVerificationCancel(content),
}
.into(),
CancelContent::ToDevice(c) => self
.content_to_request(AnyToDeviceEventContent::KeyVerificationCancel(c))
.into(),
})
}
pub(crate) fn cancel_if_timed_out(&self) -> Option<ToDeviceRequest> {
pub(crate) fn cancel_if_timed_out(&self) -> Option<OutgoingVerificationRequest> {
if self.is_canceled() || self.is_done() {
None
} else if self.timed_out() {
@ -469,7 +570,17 @@ impl Sas {
let sas: InnerSas = (*guard).clone();
let (sas, content) = sas.cancel(CancelCode::Timeout);
*guard = sas;
content.map(|c| self.content_to_request(c))
content.map(|c| match c {
CancelContent::Room(room_id, content) => RoomMessageRequest {
room_id,
txn_id: Uuid::new_v4(),
content: AnyMessageEventContent::KeyVerificationCancel(content),
}
.into(),
CancelContent::ToDevice(c) => self
.content_to_request(AnyToDeviceEventContent::KeyVerificationCancel(c))
.into(),
})
} else {
None
}
@ -512,10 +623,16 @@ impl Sas {
self.inner.lock().unwrap().decimals()
}
pub(crate) fn receive_event(
&self,
event: &mut AnyToDeviceEvent,
) -> Option<AnyToDeviceEventContent> {
pub(crate) fn receive_room_event(&self, event: &AnyMessageEvent) -> Option<OutgoingContent> {
let mut guard = self.inner.lock().unwrap();
let sas: InnerSas = (*guard).clone();
let (sas, content) = sas.receive_room_event(event);
*guard = sas;
content
}
pub(crate) fn receive_event(&self, event: &AnyToDeviceEvent) -> Option<OutgoingContent> {
let mut guard = self.inner.lock().unwrap();
let sas: InnerSas = (*guard).clone();
let (sas, content) = sas.receive_event(event);
@ -537,246 +654,11 @@ impl Sas {
}
}
#[derive(Clone, Debug)]
enum InnerSas {
Created(SasState<Created>),
Started(SasState<Started>),
Accepted(SasState<Accepted>),
KeyRecieved(SasState<KeyReceived>),
Confirmed(SasState<Confirmed>),
MacReceived(SasState<MacReceived>),
Done(SasState<Done>),
Canceled(SasState<Canceled>),
}
impl InnerSas {
fn start(
account: ReadOnlyAccount,
other_device: ReadOnlyDevice,
other_identity: Option<UserIdentities>,
) -> (InnerSas, StartToDeviceEventContent) {
let sas = SasState::<Created>::new(account, other_device, other_identity);
let content = sas.as_content();
(InnerSas::Created(sas), content)
}
fn from_start_event(
account: ReadOnlyAccount,
other_device: ReadOnlyDevice,
event: &ToDeviceEvent<StartToDeviceEventContent>,
other_identity: Option<UserIdentities>,
) -> Result<InnerSas, AnyToDeviceEventContent> {
match SasState::<Started>::from_start_event(account, other_device, event, other_identity) {
Ok(s) => Ok(InnerSas::Started(s)),
Err(s) => Err(s.as_content()),
}
}
fn accept(&self) -> Option<AcceptToDeviceEventContent> {
if let InnerSas::Started(s) = self {
Some(s.as_content())
} else {
None
}
}
#[cfg(test)]
#[allow(dead_code)]
fn set_creation_time(&mut self, time: Instant) {
match self {
InnerSas::Created(s) => s.set_creation_time(time),
InnerSas::Started(s) => s.set_creation_time(time),
InnerSas::Canceled(s) => s.set_creation_time(time),
InnerSas::Accepted(s) => s.set_creation_time(time),
InnerSas::KeyRecieved(s) => s.set_creation_time(time),
InnerSas::Confirmed(s) => s.set_creation_time(time),
InnerSas::MacReceived(s) => s.set_creation_time(time),
InnerSas::Done(s) => s.set_creation_time(time),
}
}
fn cancel(self, code: CancelCode) -> (InnerSas, Option<AnyToDeviceEventContent>) {
let sas = match self {
InnerSas::Created(s) => s.cancel(code),
InnerSas::Started(s) => s.cancel(code),
InnerSas::Accepted(s) => s.cancel(code),
InnerSas::KeyRecieved(s) => s.cancel(code),
InnerSas::MacReceived(s) => s.cancel(code),
_ => return (self, None),
};
let content = sas.as_content();
(InnerSas::Canceled(sas), Some(content))
}
fn confirm(self) -> (InnerSas, Option<MacToDeviceEventContent>) {
match self {
InnerSas::KeyRecieved(s) => {
let sas = s.confirm();
let content = sas.as_content();
(InnerSas::Confirmed(sas), Some(content))
}
InnerSas::MacReceived(s) => {
let sas = s.confirm();
let content = sas.as_content();
(InnerSas::Done(sas), Some(content))
}
_ => (self, None),
}
}
fn receive_event(
self,
event: &mut AnyToDeviceEvent,
) -> (InnerSas, Option<AnyToDeviceEventContent>) {
match event {
AnyToDeviceEvent::KeyVerificationAccept(e) => {
if let InnerSas::Created(s) = self {
match s.into_accepted(e) {
Ok(s) => {
let content = s.as_content();
(
InnerSas::Accepted(s),
Some(AnyToDeviceEventContent::KeyVerificationKey(content)),
)
}
Err(s) => {
let content = s.as_content();
(InnerSas::Canceled(s), Some(content))
}
}
} else {
(self, None)
}
}
AnyToDeviceEvent::KeyVerificationKey(e) => match self {
InnerSas::Accepted(s) => match s.into_key_received(e) {
Ok(s) => (InnerSas::KeyRecieved(s), None),
Err(s) => {
let content = s.as_content();
(InnerSas::Canceled(s), Some(content))
}
},
InnerSas::Started(s) => match s.into_key_received(e) {
Ok(s) => {
let content = s.as_content();
(
InnerSas::KeyRecieved(s),
Some(AnyToDeviceEventContent::KeyVerificationKey(content)),
)
}
Err(s) => {
let content = s.as_content();
(InnerSas::Canceled(s), Some(content))
}
},
_ => (self, None),
},
AnyToDeviceEvent::KeyVerificationMac(e) => match self {
InnerSas::KeyRecieved(s) => match s.into_mac_received(e) {
Ok(s) => (InnerSas::MacReceived(s), None),
Err(s) => {
let content = s.as_content();
(InnerSas::Canceled(s), Some(content))
}
},
InnerSas::Confirmed(s) => match s.into_done(e) {
Ok(s) => (InnerSas::Done(s), None),
Err(s) => {
let content = s.as_content();
(InnerSas::Canceled(s), Some(content))
}
},
_ => (self, None),
},
_ => (self, None),
}
}
fn can_be_presented(&self) -> bool {
match self {
InnerSas::KeyRecieved(_) => true,
InnerSas::MacReceived(_) => true,
_ => false,
}
}
fn is_done(&self) -> bool {
matches!(self, InnerSas::Done(_))
}
fn is_canceled(&self) -> bool {
matches!(self, InnerSas::Canceled(_))
}
fn timed_out(&self) -> bool {
match self {
InnerSas::Created(s) => s.timed_out(),
InnerSas::Started(s) => s.timed_out(),
InnerSas::Canceled(s) => s.timed_out(),
InnerSas::Accepted(s) => s.timed_out(),
InnerSas::KeyRecieved(s) => s.timed_out(),
InnerSas::Confirmed(s) => s.timed_out(),
InnerSas::MacReceived(s) => s.timed_out(),
InnerSas::Done(s) => s.timed_out(),
}
}
fn verification_flow_id(&self) -> Arc<str> {
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<Vec<(&'static str, &'static str)>> {
match self {
InnerSas::KeyRecieved(s) => Some(s.get_emoji()),
InnerSas::MacReceived(s) => Some(s.get_emoji()),
_ => None,
}
}
fn decimals(&self) -> Option<(u16, u16, u16)> {
match self {
InnerSas::KeyRecieved(s) => Some(s.get_decimal()),
InnerSas::MacReceived(s) => Some(s.get_decimal()),
_ => None,
}
}
fn verified_devices(&self) -> Option<Arc<[ReadOnlyDevice]>> {
if let InnerSas::Done(s) = self {
Some(s.verified_devices())
} else {
None
}
}
fn verified_identities(&self) -> Option<Arc<[UserIdentities]>> {
if let InnerSas::Done(s) = self {
Some(s.verified_identities())
} else {
None
}
}
}
#[cfg(test)]
mod test {
use std::{convert::TryFrom, sync::Arc};
use matrix_sdk_common::{
events::{EventContent, ToDeviceEvent},
identifiers::{DeviceId, UserId},
};
use matrix_sdk_common::identifiers::{DeviceId, UserId};
use crate::{
olm::PrivateCrossSigningIdentity,
@ -785,7 +667,7 @@ mod test {
ReadOnlyAccount, ReadOnlyDevice,
};
use super::{Accepted, Created, Sas, SasState, Started};
use super::Sas;
fn alice_id() -> UserId {
UserId::try_from("@alice:example.org").unwrap()
@ -803,97 +685,6 @@ mod test {
"BOBDEVCIE".into()
}
fn wrap_to_device_event<C: EventContent>(sender: &UserId, content: C) -> ToDeviceEvent<C> {
ToDeviceEvent {
sender: sender.clone(),
content,
}
}
async fn get_sas_pair() -> (SasState<Created>, SasState<Started>) {
let alice = ReadOnlyAccount::new(&alice_id(), &alice_device_id());
let alice_device = ReadOnlyDevice::from_account(&alice).await;
let bob = ReadOnlyAccount::new(&bob_id(), &bob_device_id());
let bob_device = ReadOnlyDevice::from_account(&bob).await;
let alice_sas = SasState::<Created>::new(alice.clone(), bob_device, None);
let start_content = alice_sas.as_content();
let event = wrap_to_device_event(alice_sas.user_id(), start_content);
let bob_sas =
SasState::<Started>::from_start_event(bob.clone(), alice_device, &event, None);
(alice_sas, bob_sas.unwrap())
}
#[tokio::test]
async fn create_sas() {
let (_, _) = get_sas_pair().await;
}
#[tokio::test]
async fn sas_accept() {
let (alice, bob) = get_sas_pair().await;
let event = wrap_to_device_event(bob.user_id(), bob.as_content());
alice.into_accepted(&event).unwrap();
}
#[tokio::test]
async fn sas_key_share() {
let (alice, bob) = get_sas_pair().await;
let event = wrap_to_device_event(bob.user_id(), bob.as_content());
let alice: SasState<Accepted> = alice.into_accepted(&event).unwrap();
let mut event = wrap_to_device_event(alice.user_id(), alice.as_content());
let bob = bob.into_key_received(&mut event).unwrap();
let mut event = wrap_to_device_event(bob.user_id(), bob.as_content());
let alice = alice.into_key_received(&mut event).unwrap();
assert_eq!(alice.get_decimal(), bob.get_decimal());
assert_eq!(alice.get_emoji(), bob.get_emoji());
}
#[tokio::test]
async fn sas_full() {
let (alice, bob) = get_sas_pair().await;
let event = wrap_to_device_event(bob.user_id(), bob.as_content());
let alice: SasState<Accepted> = alice.into_accepted(&event).unwrap();
let mut event = wrap_to_device_event(alice.user_id(), alice.as_content());
let bob = bob.into_key_received(&mut event).unwrap();
let mut event = wrap_to_device_event(bob.user_id(), bob.as_content());
let alice = alice.into_key_received(&mut event).unwrap();
assert_eq!(alice.get_decimal(), bob.get_decimal());
assert_eq!(alice.get_emoji(), bob.get_emoji());
let bob = bob.confirm();
let event = wrap_to_device_event(bob.user_id(), bob.as_content());
let alice = alice.into_mac_received(&event).unwrap();
assert!(!alice.get_emoji().is_empty());
let alice = alice.confirm();
let event = wrap_to_device_event(alice.user_id(), alice.as_content());
let bob = bob.into_done(&event).unwrap();
assert!(bob.verified_devices().contains(&bob.other_device()));
assert!(alice.verified_devices().contains(&alice.other_device()));
}
#[tokio::test]
async fn sas_wrapper_full() {
let alice = ReadOnlyAccount::new(&alice_id(), &alice_device_id());
@ -916,50 +707,48 @@ mod test {
alice_store,
None,
);
let event = wrap_to_device_event(alice.user_id(), content);
let bob = Sas::from_start_event(
bob,
PrivateCrossSigningIdentity::empty(bob_id()),
alice_device,
bob_store,
&event,
content,
None,
)
.unwrap();
let mut event = wrap_any_to_device_content(
let event = wrap_any_to_device_content(
bob.user_id(),
get_content_from_request(&bob.accept().unwrap()),
);
let content = alice.receive_event(&mut event);
let content = alice.receive_event(&event);
assert!(!alice.can_be_presented());
assert!(!bob.can_be_presented());
let mut event = wrap_any_to_device_content(alice.user_id(), content.unwrap());
let mut event =
wrap_any_to_device_content(bob.user_id(), bob.receive_event(&mut event).unwrap());
let event = wrap_any_to_device_content(alice.user_id(), content.unwrap());
let event = wrap_any_to_device_content(bob.user_id(), bob.receive_event(&event).unwrap());
assert!(bob.can_be_presented());
alice.receive_event(&mut event);
alice.receive_event(&event);
assert!(alice.can_be_presented());
assert_eq!(alice.emoji().unwrap(), bob.emoji().unwrap());
assert_eq!(alice.decimals().unwrap(), bob.decimals().unwrap());
let mut event = wrap_any_to_device_content(
let event = wrap_any_to_device_content(
alice.user_id(),
get_content_from_request(&alice.confirm().await.unwrap().0.unwrap()),
);
bob.receive_event(&mut event);
bob.receive_event(&event);
let mut event = wrap_any_to_device_content(
let event = wrap_any_to_device_content(
bob.user_id(),
get_content_from_request(&bob.confirm().await.unwrap().0.unwrap()),
);
alice.receive_event(&mut event);
alice.receive_event(&event);
assert!(alice
.verified_devices()

File diff suppressed because it is too large Load diff

View file

@ -355,6 +355,7 @@ pub enum SyncResponseFile {
DefaultWithSummary,
Invite,
Leave,
Voip,
}
/// Get specific API responses for testing
@ -365,6 +366,7 @@ pub fn sync_response(kind: SyncResponseFile) -> SyncResponse {
SyncResponseFile::DefaultWithSummary => &test_json::DEFAULT_SYNC_SUMMARY,
SyncResponseFile::Invite => &test_json::INVITE_SYNC,
SyncResponseFile::Leave => &test_json::LEAVE_SYNC,
SyncResponseFile::Voip => &test_json::VOIP_SYNC,
};
let response = Response::builder()

View file

@ -16,7 +16,9 @@ pub use events::{
REACTION, REDACTED, REDACTED_INVALID, REDACTED_STATE, REDACTION, REGISTRATION_RESPONSE_ERR,
ROOM_ID, ROOM_MESSAGES, TYPING,
};
pub use sync::{DEFAULT_SYNC_SUMMARY, INVITE_SYNC, LEAVE_SYNC, LEAVE_SYNC_EVENT, MORE_SYNC, SYNC};
pub use sync::{
DEFAULT_SYNC_SUMMARY, INVITE_SYNC, LEAVE_SYNC, LEAVE_SYNC_EVENT, MORE_SYNC, SYNC, VOIP_SYNC,
};
lazy_static! {
pub static ref DEVICES: JsonValue = json!({

View file

@ -1103,3 +1103,123 @@ lazy_static! {
"next_batch": "s1380317562_757269739_1655566_503953763_334052043_1209862_55290918_65705002_101146"
});
}
lazy_static! {
pub static ref VOIP_SYNC: JsonValue = json!({
"device_one_time_keys_count": {},
"next_batch": "s526_47314_0_7_1_1_1_11444_1",
"device_lists": {
"changed": [
"@example:example.org"
],
"left": []
},
"rooms": {
"invite": {},
"join": {
"!SVkFJHzfwvuaIEawgC:localhost": {
"summary": {},
"account_data": {
"events": []
},
"ephemeral": {
"events": [ ]
},
"state": {
"events": []
},
"timeline": {
"events": [
{
"content": {
"call_id": "12345",
"lifetime": 60000,
"offer": {
"sdp": "v=0\r\no=- 6584580628695956864 2 IN IP4 127.0.0.1[...]",
"type": "offer"
},
"version": 0
},
"event_id": "$143273582443PhrSn:example.org",
"origin_server_ts": 143273582,
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
"sender": "@example:example.org",
"type": "m.call.invite",
"unsigned": {
"age": 1234
}
},
{
"content": {
"answer": {
"sdp": "v=0\r\no=- 6584580628695956864 2 IN IP4 127.0.0.1[...]",
"type": "answer"
},
"call_id": "12345",
"lifetime": 60000,
"version": 0
},
"event_id": "$143273582443PhrSn:example.org",
"origin_server_ts": 143273582,
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
"sender": "@example:example.org",
"type": "m.call.answer",
"unsigned": {
"age": 1234
}
},
{
"content": {
"call_id": "12345",
"candidates": [
{
"candidate": "candidate:863018703 1 udp 2122260223 10.9.64.156 43670 typ host generation 0",
"sdpMLineIndex": 0,
"sdpMid": "audio"
}
],
"version": 0
},
"event_id": "$143273582443PhrSn:example.org",
"origin_server_ts": 143273582,
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
"sender": "@example:example.org",
"type": "m.call.candidates",
"unsigned": {
"age": 1234
}
},
{
"content": {
"call_id": "12345",
"version": 0
},
"event_id": "$143273582443PhrSn:example.org",
"origin_server_ts": 143273582,
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
"sender": "@example:example.org",
"type": "m.call.hangup",
"unsigned": {
"age": 1234
}
}
],
"limited": true,
"prev_batch": "t392-516_47314_0_7_1_1_1_11444_1"
},
"unread_notifications": {
"highlight_count": 0,
"notification_count": 11
}
}
},
"leave": {}
},
"to_device": {
"events": []
},
"presence": {
"events": []
}
});
}