crypto: WIP key verification request handling.

This commit is contained in:
Damir Jelić 2020-12-09 17:18:23 +01:00
parent 5babd71341
commit 7198b0daba
12 changed files with 439 additions and 25 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 {
let e = event
@ -118,6 +132,30 @@ 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 Ok(AnySyncRoomEvent::Message(AnySyncMessageEvent::RoomMessage(m))) =
event.deserialize()
{
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");
}
}
}
}
}
initial.store(false, Ordering::SeqCst);
LoopCtrl::Continue
})
.await;

View file

@ -118,6 +118,7 @@ use matrix_sdk_common::{
use crate::{
http_client::{client_with_config, HttpClient, HttpSend},
verification_request::VerificationRequest,
Error, EventEmitter, OutgoingRequest, Result,
};
@ -1894,6 +1895,19 @@ impl Client {
})
}
/// 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(),
})
}
/// Get a specific device of a user.
///
/// # Arguments

View file

@ -88,6 +88,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

@ -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

@ -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

@ -53,4 +53,4 @@ pub(crate) use olm::ReadOnlyAccount;
pub use requests::{
IncomingResponse, KeysQueryRequest, OutgoingRequest, OutgoingRequests, 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.
@ -767,6 +768,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;
}
@ -924,6 +930,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)
}

View file

@ -16,16 +16,23 @@ use std::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, AnySyncMessageEvent, AnySyncRoomEvent,
AnyToDeviceEvent, AnyToDeviceEventContent,
},
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, Sas, VerificationResult},
};
use crate::{
olm::PrivateCrossSigningIdentity,
requests::{OutgoingRequest, ToDeviceRequest},
@ -39,6 +46,7 @@ pub struct VerificationMachine {
private_identity: Arc<Mutex<PrivateCrossSigningIdentity>>,
pub(crate) store: Arc<Box<dyn CryptoStore>>,
verifications: Arc<DashMap<String, Sas>>,
requests: Arc<DashMap<EventId, VerificationRequest>>,
outgoing_to_device_messages: Arc<DashMap<Uuid, OutgoingRequest>>,
}
@ -52,8 +60,9 @@ 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(),
}
}
@ -84,6 +93,11 @@ impl VerificationMachine {
Ok((sas, request))
}
pub fn get_request(&self, flow_id: &EventId) -> Option<VerificationRequest> {
#[allow(clippy::map_clone)]
self.requests.get(flow_id).map(|s| s.clone())
}
pub fn get_sas(&self, transaction_id: &str) -> Option<Sas> {
#[allow(clippy::map_clone)]
self.verifications.get(transaction_id).map(|s| s.clone())
@ -106,7 +120,7 @@ impl VerificationMachine {
self.outgoing_to_device_messages.insert(request_id, request);
}
fn receive_event_helper(&self, sas: &Sas, event: &mut AnyToDeviceEvent) {
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);
}
@ -141,10 +155,40 @@ impl VerificationMachine {
}
}
pub async fn receive_event(
pub async fn receive_room_event(
&self,
event: &mut AnyToDeviceEvent,
room_id: &RoomId,
event: &AnySyncRoomEvent,
) -> Result<(), CryptoStoreError> {
match event {
AnySyncRoomEvent::Message(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(
room_id,
self.account.user_id(),
self.account.device_id(),
&m.sender,
&m.event_id,
r,
);
self.requests.insert(m.event_id.clone(), request);
}
}
}
_ => (),
}
Ok(())
}
pub async fn receive_event(&self, event: &AnyToDeviceEvent) -> Result<(), CryptoStoreError> {
trace!("Received a key verification event {:?}", event);
match event {

View file

@ -13,9 +13,11 @@
// 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)]

View file

@ -0,0 +1,262 @@
// 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, Relation, VerificationMethod},
room::message::KeyVerificationRequestEventContent,
},
identifiers::{DeviceId, DeviceIdBox, EventId, RoomId, UserId},
};
const SUPPORTED_METHODS: &[VerificationMethod] = &[VerificationMethod::MSasV1];
#[derive(Clone, Debug)]
/// TODO
pub struct VerificationRequest {
inner: Arc<Mutex<InnerRequest>>,
room_id: Arc<RoomId>,
}
impl VerificationRequest {
pub(crate) fn from_request_event(
room_id: &RoomId,
own_user_id: &UserId,
own_device_id: &DeviceId,
sender: &UserId,
event_id: &EventId,
content: &KeyVerificationRequestEventContent,
) -> Self {
Self {
inner: Arc::new(Mutex::new(InnerRequest::Requested(
RequestState::from_request_event(
own_user_id,
own_device_id,
sender,
event_id,
content,
),
))),
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()
}
}
#[derive(Debug)]
enum InnerRequest {
Created(RequestState<Created>),
Sent(RequestState<Sent>),
Requested(RequestState<Requested>),
Ready(RequestState<Ready>),
Accepted(RequestState<Accepted>),
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::Accepted(state);
Some(content)
} else {
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 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(),
}
.into(),
}
}
}
#[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.clone(),
}
.into(),
}
}
}
#[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(),
}
.into(),
}
}
fn accept(self) -> (RequestState<Accepted>, ReadyEventContent) {
// TODO let the user pick a method here.
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: Accepted {
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,
}
#[derive(Clone, Debug)]
struct Accepted {
/// The verification methods that were accepted
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,
}
#[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,
}

View file

@ -514,7 +514,7 @@ impl Sas {
pub(crate) fn receive_event(
&self,
event: &mut AnyToDeviceEvent,
event: &AnyToDeviceEvent,
) -> Option<AnyToDeviceEventContent> {
let mut guard = self.inner.lock().unwrap();
let sas: InnerSas = (*guard).clone();
@ -628,7 +628,7 @@ impl InnerSas {
fn receive_event(
self,
event: &mut AnyToDeviceEvent,
event: &AnyToDeviceEvent,
) -> (InnerSas, Option<AnyToDeviceEventContent>) {
match event {
AnyToDeviceEvent::KeyVerificationAccept(e) => {

View file

@ -14,7 +14,6 @@
use std::{
convert::TryFrom,
mem,
sync::{Arc, Mutex},
time::{Duration, Instant},
};
@ -498,14 +497,14 @@ impl SasState<Started> {
/// anymore.
pub fn into_key_received(
self,
event: &mut ToDeviceEvent<KeyToDeviceEventContent>,
event: &ToDeviceEvent<KeyToDeviceEventContent>,
) -> Result<SasState<KeyReceived>, SasState<Canceled>> {
self.check_event(&event.sender, &event.content.transaction_id)
.map_err(|c| self.clone().cancel(c))?;
let accepted_protocols = AcceptedProtocols::default();
let their_pubkey = mem::take(&mut event.content.key);
let their_pubkey = event.content.key.clone();
self.inner
.lock()
@ -539,7 +538,7 @@ impl SasState<Accepted> {
/// anymore.
pub fn into_key_received(
self,
event: &mut ToDeviceEvent<KeyToDeviceEventContent>,
event: &ToDeviceEvent<KeyToDeviceEventContent>,
) -> Result<SasState<KeyReceived>, SasState<Canceled>> {
self.check_event(&event.sender, &event.content.transaction_id)
.map_err(|c| self.clone().cancel(c))?;
@ -549,7 +548,7 @@ impl SasState<Accepted> {
if self.state.commitment != commitment {
Err(self.cancel(CancelCode::InvalidMessage))
} else {
let their_pubkey = mem::take(&mut event.content.key);
let their_pubkey = event.content.key.clone();
self.inner
.lock()