From 692f9baa0eaab9bbbc594b85231afa808f80b3e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Thu, 17 Sep 2020 16:09:08 +0200 Subject: [PATCH] crypto: Add logic to handle outgoing key requests. --- matrix_sdk_crypto/src/key_request.rs | 193 ++++++++++++------ .../src/olm/group_sessions/inbound.rs | 39 +++- 2 files changed, 174 insertions(+), 58 deletions(-) diff --git a/matrix_sdk_crypto/src/key_request.rs b/matrix_sdk_crypto/src/key_request.rs index 760a91f7..66fefae8 100644 --- a/matrix_sdk_crypto/src/key_request.rs +++ b/matrix_sdk_crypto/src/key_request.rs @@ -1,22 +1,28 @@ -/// TODO -/// -/// Make a state machine that handles key requests. -/// -/// Start with the outgoing key requests. We need to queue up a request and -/// store the outgoing key requests, store if the request was sent out. -/// Once we receive a key, check if we have an outgoing requests and if so -/// accept and store the key if we don't have a better one. Send out a -/// key request cancelation if we receive one. -/// -/// Incoming key requests: -/// First handle the easy case, if we trust the device and have a session, queue -/// up a to-device request. -/// -/// If we don't have a session, queue up a key claim request, once we get a -/// session send out the key if we trust the device. -/// -/// If we don't trust the device store an object that remembers the request and -/// let the users introspect that object. +// 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. + +// TODO +// +// Incoming key requests: +// First handle the easy case, if we trust the device and have a session, queue +// up a to-device request. +// +// If we don't have a session, queue up a key claim request, once we get a +// session send out the key if we trust the device. +// +// If we don't trust the device store an object that remembers the request and +// let the users introspect that object. use dashmap::DashMap; use serde::{Deserialize, Serialize}; use serde_json::value::to_raw_value; @@ -35,8 +41,9 @@ use matrix_sdk_common::{ }; use crate::{ + olm::InboundGroupSession, requests::{OutgoingRequest, ToDeviceRequest}, - store::Store, + store::{CryptoStoreError, Store}, }; #[derive(Debug, Clone)] @@ -77,12 +84,35 @@ impl Encode for ForwardedRoomKeyEventContent { } impl KeyRequestMachine { + pub fn new(user_id: Arc, device_id: Arc, store: Store) -> Self { + Self { + user_id, + device_id, + store, + outgoing_to_device_requests: Arc::new(DashMap::new()), + } + } + + /// Create a new outgoing key request for the key with the given session id. + /// + /// This will queue up a new to-device request and store the key info so + /// once we receive a forwarded room key we can check that it matches the + /// key we requested. + /// + /// This does nothing if a request for this key has already been sent out. + /// + /// # Arguments + /// * `room_id` - The id of the room where the key is used in. + /// + /// * `sender_key` - The curve25519 key of the sender that owns the key. + /// + /// * `session_id` - The id that uniquely identifies the session. async fn create_outgoing_key_request( &self, room_id: &RoomId, sender_key: &str, session_id: &str, - ) { + ) -> Result<(), CryptoStoreError> { let key_info = RequestedKeyInfo { algorithm: EventEncryptionAlgorithm::MegolmV1AesSha2, room_id: room_id.to_owned(), @@ -90,11 +120,11 @@ impl KeyRequestMachine { session_id: session_id.to_owned(), }; - let id: Option = self.store.get_object(&key_info.encode()).await.unwrap(); + let id: Option = self.store.get_object(&key_info.encode()).await?; if id.is_some() { // We already sent out a request for this key, nothing to do. - return; + return Ok(()); } info!("Creating new outgoing room key request {:#?}", key_info); @@ -113,10 +143,7 @@ impl KeyRequestMachine { messages .entry((&*self.user_id).to_owned()) .or_insert_with(BTreeMap::new) - .insert( - DeviceIdOrAllDevices::AllDevices, - to_raw_value(&content).unwrap(), - ); + .insert(DeviceIdOrAllDevices::AllDevices, to_raw_value(&content)?); let request = OutgoingRequest { request_id: id, @@ -136,73 +163,125 @@ impl KeyRequestMachine { sent_out: false, }; - self.save_outgoing_key_info(id, info).await; + self.save_outgoing_key_info(id, info).await?; self.outgoing_to_device_requests.insert(id, request); + + Ok(()) } - async fn save_outgoing_key_info(&self, id: Uuid, info: OugoingKeyInfo) { - // We need to access the key info via the hash of an RequestedKeyInfo - // and with the unique request id. - // Once to mark the request as sent. And another time to check if the - // received forwarded key matches to a request. + /// Save an outgoing key info. + async fn save_outgoing_key_info( + &self, + id: Uuid, + info: OugoingKeyInfo, + ) -> Result<(), CryptoStoreError> { + // TODO we'll want to use a transaction to store those atomically. + // To allow this we'll need to rework our cryptostore trait to return + // a transaction trait and the transaction trait will have the save_X + // methods. let id_string = id.to_string(); - self.store.save_object(&id_string, &info).await.unwrap(); - self.store - .save_object(&info.info.encode(), &id) - .await - .unwrap(); + self.store.save_object(&id_string, &info).await?; + self.store.save_object(&info.info.encode(), &id).await?; + + Ok(()) } - async fn get_key_info(&self, content: &ForwardedRoomKeyEventContent) -> Option { - let id: Option = self.store.get_object(&content.encode()).await.unwrap(); + /// Get an outgoing key info that matches the forwarded room key content. + async fn get_key_info( + &self, + content: &ForwardedRoomKeyEventContent, + ) -> Result, CryptoStoreError> { + let id: Option = self.store.get_object(&content.encode()).await?; if let Some(id) = id { - self.store.get_object(&id.to_string()).await.unwrap() + self.store.get_object(&id.to_string()).await } else { - None + Ok(None) } } - async fn delete_key_info(&self, info: OugoingKeyInfo) { + /// Delete the given outgoing key info. + async fn delete_key_info(&self, info: OugoingKeyInfo) -> Result<(), CryptoStoreError> { self.store .delete_object(&info.request_id.to_string()) - .await - .unwrap(); - self.store.delete_object(&info.info.encode()).await.unwrap(); + .await?; + self.store.delete_object(&info.info.encode()).await?; + + Ok(()) } - async fn mark_outgoing_request_as_sent(&self, id: Uuid) { + /// Mark the outgoing request as sent. + async fn mark_outgoing_request_as_sent(&self, id: Uuid) -> Result<(), CryptoStoreError> { self.outgoing_to_device_requests.remove(&id); - let info: Option = self.store.get_object(&id.to_string()).await.unwrap(); + let info: Option = self.store.get_object(&id.to_string()).await?; if let Some(mut info) = info { trace!("Marking outgoing key request as sent {:#?}", info); info.sent_out = true; - self.save_outgoing_key_info(id, info).await; + self.save_outgoing_key_info(id, info).await?; } else { error!("Trying to mark a room key request with the id {} as sent, but no key info was found", id); } + + Ok(()) } + /// Save an inbound group session we received using a key forward. + /// + /// At the same time delete the key info since we received the wanted key. + async fn save_session( + &self, + key_info: OugoingKeyInfo, + session: InboundGroupSession, + ) -> Result<(), CryptoStoreError> { + // TODO perhaps only remove the key info if the first known index is 0. + self.store.save_inbound_group_sessions(&[session]).await?; + self.outgoing_to_device_requests + .remove(&key_info.request_id); + self.delete_key_info(key_info).await + } + + /// Receive a forwarded room key event. async fn receive_forwarded_room_key( &self, - _sender_key: &str, - _signing_key: &str, + sender_key: &str, event: &mut ToDeviceEvent, - ) { - let key_info = self.get_key_info(&event.content).await; + ) -> Result<(), CryptoStoreError> { + let key_info = self.get_key_info(&event.content).await?; if let Some(info) = key_info { - // TODO create a new room key and store it if it's a better version - // of the existing key or if we don't have it at all. - self.outgoing_to_device_requests.remove(&info.request_id); - self.delete_key_info(info).await; + let session = InboundGroupSession::from_forwarded_key(sender_key, &event.content)?; + + let old_session = self + .store + .get_inbound_group_session( + session.room_id(), + &session.sender_key, + session.session_id(), + ) + .await?; + + // If we have a previous session, check if we have a better version + // and store the new one if so. + if let Some(old_session) = old_session { + let first_old_index = old_session.first_known_index().await; + let first_index = session.first_known_index().await; + + if first_old_index > first_index { + self.save_session(info, session).await?; + } + // If we didn't have a previous session, store it. + } else { + self.save_session(info, session).await?; + } } else { info!( "Received a forwarded room key from {}, but no key info was found.", event.sender, ); } + + Ok(()) } fn handle_incoming_key_request(&self, event: &ToDeviceEvent) { diff --git a/matrix_sdk_crypto/src/olm/group_sessions/inbound.rs b/matrix_sdk_crypto/src/olm/group_sessions/inbound.rs index 733b29fe..63830d4c 100644 --- a/matrix_sdk_crypto/src/olm/group_sessions/inbound.rs +++ b/matrix_sdk_crypto/src/olm/group_sessions/inbound.rs @@ -20,7 +20,10 @@ use std::{ }; use matrix_sdk_common::{ - events::{room::encrypted::EncryptedEventContent, AnySyncRoomEvent, SyncMessageEvent}, + events::{ + forwarded_room_key::ForwardedRoomKeyEventContent, room::encrypted::EncryptedEventContent, + AnySyncRoomEvent, SyncMessageEvent, + }, identifiers::{DeviceKeyAlgorithm, EventEncryptionAlgorithm, RoomId}, locks::Mutex, Raw, @@ -113,6 +116,40 @@ impl InboundGroupSession { Self::try_from(exported_session.into()) } + /// Create a new inbound group session from a forwarded room key content. + /// + /// # Arguments + /// + /// * `sender_key` - The public curve25519 key of the account that + /// sent us the session + /// + /// * `content` - A forwarded room key content that contains the session key + /// to create the `InboundGroupSession`. + pub(crate) fn from_forwarded_key( + sender_key: &str, + content: &ForwardedRoomKeyEventContent, + ) -> Result { + let session = OlmInboundGroupSession::import(&content.session_key)?; + let mut forwarding_chains = content.forwarding_curve25519_key_chain.clone(); + forwarding_chains.push(sender_key.to_owned()); + + let mut sender_claimed_key = BTreeMap::new(); + sender_claimed_key.insert( + DeviceKeyAlgorithm::Ed25519, + content.sender_claimed_ed25519_key.to_owned(), + ); + + Ok(InboundGroupSession { + inner: Arc::new(Mutex::new(session)), + session_id: Arc::new(content.session_id.clone()), + sender_key: Arc::new(content.sender_key.clone()), + signing_key: Arc::new(sender_claimed_key), + room_id: Arc::new(content.room_id.clone()), + forwarding_chains: Arc::new(Mutex::new(Some(forwarding_chains))), + imported: Arc::new(true), + }) + } + /// Store the group session as a base64 encoded string. /// /// # Arguments