crypto: Add logic to handle outgoing key requests.
parent
6b24d91ed9
commit
692f9baa0e
|
@ -1,22 +1,28 @@
|
||||||
/// TODO
|
// Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||||
///
|
//
|
||||||
/// Make a state machine that handles key requests.
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
///
|
// you may not use this file except in compliance with the License.
|
||||||
/// Start with the outgoing key requests. We need to queue up a request and
|
// You may obtain a copy of the License at
|
||||||
/// 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
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
/// accept and store the key if we don't have a better one. Send out a
|
//
|
||||||
/// key request cancelation if we receive one.
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
///
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
/// Incoming key requests:
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
/// First handle the easy case, if we trust the device and have a session, queue
|
// See the License for the specific language governing permissions and
|
||||||
/// up a to-device request.
|
// limitations under the License.
|
||||||
///
|
|
||||||
/// If we don't have a session, queue up a key claim request, once we get a
|
// TODO
|
||||||
/// session send out the key if we trust the device.
|
//
|
||||||
///
|
// Incoming key requests:
|
||||||
/// If we don't trust the device store an object that remembers the request and
|
// First handle the easy case, if we trust the device and have a session, queue
|
||||||
/// let the users introspect that object.
|
// 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 dashmap::DashMap;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use serde_json::value::to_raw_value;
|
use serde_json::value::to_raw_value;
|
||||||
|
@ -35,8 +41,9 @@ use matrix_sdk_common::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
olm::InboundGroupSession,
|
||||||
requests::{OutgoingRequest, ToDeviceRequest},
|
requests::{OutgoingRequest, ToDeviceRequest},
|
||||||
store::Store,
|
store::{CryptoStoreError, Store},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
|
@ -77,12 +84,35 @@ impl Encode for ForwardedRoomKeyEventContent {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl KeyRequestMachine {
|
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(
|
async fn create_outgoing_key_request(
|
||||||
&self,
|
&self,
|
||||||
room_id: &RoomId,
|
room_id: &RoomId,
|
||||||
sender_key: &str,
|
sender_key: &str,
|
||||||
session_id: &str,
|
session_id: &str,
|
||||||
) {
|
) -> Result<(), CryptoStoreError> {
|
||||||
let key_info = RequestedKeyInfo {
|
let key_info = RequestedKeyInfo {
|
||||||
algorithm: EventEncryptionAlgorithm::MegolmV1AesSha2,
|
algorithm: EventEncryptionAlgorithm::MegolmV1AesSha2,
|
||||||
room_id: room_id.to_owned(),
|
room_id: room_id.to_owned(),
|
||||||
|
@ -90,11 +120,11 @@ impl KeyRequestMachine {
|
||||||
session_id: session_id.to_owned(),
|
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() {
|
if id.is_some() {
|
||||||
// We already sent out a request for this key, nothing to do.
|
// We already sent out a request for this key, nothing to do.
|
||||||
return;
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
info!("Creating new outgoing room key request {:#?}", key_info);
|
info!("Creating new outgoing room key request {:#?}", key_info);
|
||||||
|
@ -113,10 +143,7 @@ impl KeyRequestMachine {
|
||||||
messages
|
messages
|
||||||
.entry((&*self.user_id).to_owned())
|
.entry((&*self.user_id).to_owned())
|
||||||
.or_insert_with(BTreeMap::new)
|
.or_insert_with(BTreeMap::new)
|
||||||
.insert(
|
.insert(DeviceIdOrAllDevices::AllDevices, to_raw_value(&content)?);
|
||||||
DeviceIdOrAllDevices::AllDevices,
|
|
||||||
to_raw_value(&content).unwrap(),
|
|
||||||
);
|
|
||||||
|
|
||||||
let request = OutgoingRequest {
|
let request = OutgoingRequest {
|
||||||
request_id: id,
|
request_id: id,
|
||||||
|
@ -136,73 +163,125 @@ impl KeyRequestMachine {
|
||||||
sent_out: false,
|
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);
|
self.outgoing_to_device_requests.insert(id, request);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn save_outgoing_key_info(&self, id: Uuid, info: OugoingKeyInfo) {
|
/// Save an outgoing key info.
|
||||||
// We need to access the key info via the hash of an RequestedKeyInfo
|
async fn save_outgoing_key_info(
|
||||||
// and with the unique request id.
|
&self,
|
||||||
// Once to mark the request as sent. And another time to check if the
|
id: Uuid,
|
||||||
// received forwarded key matches to a request.
|
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();
|
let id_string = id.to_string();
|
||||||
self.store.save_object(&id_string, &info).await.unwrap();
|
self.store.save_object(&id_string, &info).await?;
|
||||||
self.store
|
self.store.save_object(&info.info.encode(), &id).await?;
|
||||||
.save_object(&info.info.encode(), &id)
|
|
||||||
.await
|
Ok(())
|
||||||
.unwrap();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_key_info(&self, content: &ForwardedRoomKeyEventContent) -> Option<OugoingKeyInfo> {
|
/// Get an outgoing key info that matches the forwarded room key content.
|
||||||
let id: Option<Uuid> = self.store.get_object(&content.encode()).await.unwrap();
|
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 {
|
if let Some(id) = id {
|
||||||
self.store.get_object(&id.to_string()).await.unwrap()
|
self.store.get_object(&id.to_string()).await
|
||||||
} else {
|
} 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
|
self.store
|
||||||
.delete_object(&info.request_id.to_string())
|
.delete_object(&info.request_id.to_string())
|
||||||
.await
|
.await?;
|
||||||
.unwrap();
|
self.store.delete_object(&info.info.encode()).await?;
|
||||||
self.store.delete_object(&info.info.encode()).await.unwrap();
|
|
||||||
|
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);
|
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 {
|
if let Some(mut info) = info {
|
||||||
trace!("Marking outgoing key request as sent {:#?}", info);
|
trace!("Marking outgoing key request as sent {:#?}", info);
|
||||||
info.sent_out = true;
|
info.sent_out = true;
|
||||||
self.save_outgoing_key_info(id, info).await;
|
self.save_outgoing_key_info(id, info).await?;
|
||||||
} else {
|
} else {
|
||||||
error!("Trying to mark a room key request with the id {} as sent, but no key info was found", id);
|
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(
|
async fn receive_forwarded_room_key(
|
||||||
&self,
|
&self,
|
||||||
_sender_key: &str,
|
sender_key: &str,
|
||||||
_signing_key: &str,
|
|
||||||
event: &mut ToDeviceEvent<ForwardedRoomKeyEventContent>,
|
event: &mut ToDeviceEvent<ForwardedRoomKeyEventContent>,
|
||||||
) {
|
) -> Result<(), CryptoStoreError> {
|
||||||
let key_info = self.get_key_info(&event.content).await;
|
let key_info = self.get_key_info(&event.content).await?;
|
||||||
|
|
||||||
if let Some(info) = key_info {
|
if let Some(info) = key_info {
|
||||||
// TODO create a new room key and store it if it's a better version
|
let session = InboundGroupSession::from_forwarded_key(sender_key, &event.content)?;
|
||||||
// of the existing key or if we don't have it at all.
|
|
||||||
self.outgoing_to_device_requests.remove(&info.request_id);
|
let old_session = self
|
||||||
self.delete_key_info(info).await;
|
.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 {
|
} else {
|
||||||
info!(
|
info!(
|
||||||
"Received a forwarded room key from {}, but no key info was found.",
|
"Received a forwarded room key from {}, but no key info was found.",
|
||||||
event.sender,
|
event.sender,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_incoming_key_request(&self, event: &ToDeviceEvent<RoomKeyRequestEventContent>) {
|
fn handle_incoming_key_request(&self, event: &ToDeviceEvent<RoomKeyRequestEventContent>) {
|
||||||
|
|
|
@ -20,7 +20,10 @@ use std::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use matrix_sdk_common::{
|
use matrix_sdk_common::{
|
||||||
events::{room::encrypted::EncryptedEventContent, AnySyncRoomEvent, SyncMessageEvent},
|
events::{
|
||||||
|
forwarded_room_key::ForwardedRoomKeyEventContent, room::encrypted::EncryptedEventContent,
|
||||||
|
AnySyncRoomEvent, SyncMessageEvent,
|
||||||
|
},
|
||||||
identifiers::{DeviceKeyAlgorithm, EventEncryptionAlgorithm, RoomId},
|
identifiers::{DeviceKeyAlgorithm, EventEncryptionAlgorithm, RoomId},
|
||||||
locks::Mutex,
|
locks::Mutex,
|
||||||
Raw,
|
Raw,
|
||||||
|
@ -113,6 +116,40 @@ impl InboundGroupSession {
|
||||||
Self::try_from(exported_session.into())
|
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.
|
/// Store the group session as a base64 encoded string.
|
||||||
///
|
///
|
||||||
/// # Arguments
|
/// # Arguments
|
||||||
|
|
Loading…
Reference in New Issue