crypto: Add logic to handle outgoing key requests.

master
Damir Jelić 2020-09-17 16:09:08 +02:00
parent 6b24d91ed9
commit 692f9baa0e
2 changed files with 174 additions and 58 deletions

View File

@ -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<UserId>, device_id: Arc<DeviceIdBox>, 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<String> = self.store.get_object(&key_info.encode()).await.unwrap();
let id: Option<String> = 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<OugoingKeyInfo> {
let id: Option<Uuid> = 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<Option<OugoingKeyInfo>, CryptoStoreError> {
let id: Option<Uuid> = 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<OugoingKeyInfo> = self.store.get_object(&id.to_string()).await.unwrap();
let info: Option<OugoingKeyInfo> = 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<ForwardedRoomKeyEventContent>,
) {
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<RoomKeyRequestEventContent>) {

View File

@ -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<Self, OlmGroupSessionError> {
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