crypto: Add logic to handle outgoing key requests.
parent
6b24d91ed9
commit
692f9baa0e
|
@ -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>) {
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue