crypto: Test the full key share flow.

master
Damir Jelić 2020-09-29 14:18:03 +02:00
parent 84066d4a76
commit 8fe1eda169
7 changed files with 175 additions and 108 deletions

View File

@ -1653,7 +1653,10 @@ impl Client {
/// # let homeserver = Url::parse("http://example.com").unwrap(); /// # let homeserver = Url::parse("http://example.com").unwrap();
/// # let client = Client::new(homeserver).unwrap(); /// # let client = Client::new(homeserver).unwrap();
/// # block_on(async { /// # block_on(async {
/// let device = client.get_device(&alice, "DEVICEID".into()).await.unwrap(); /// let device = client.get_device(&alice, "DEVICEID".into())
/// .await
/// .unwrap()
/// .unwrap();
/// ///
/// println!("{:?}", device.is_trusted()); /// println!("{:?}", device.is_trusted());
/// ///

View File

@ -55,7 +55,10 @@ impl Device {
/// # let homeserver = Url::parse("http://example.com").unwrap(); /// # let homeserver = Url::parse("http://example.com").unwrap();
/// # let client = Client::new(homeserver).unwrap(); /// # let client = Client::new(homeserver).unwrap();
/// # block_on(async { /// # block_on(async {
/// let device = client.get_device(&alice, "DEVICEID".into()).await.unwrap(); /// let device = client.get_device(&alice, "DEVICEID".into())
/// .await
/// .unwrap()
/// .unwrap();
/// ///
/// let verification = device.start_verification().await.unwrap(); /// let verification = device.start_verification().await.unwrap();
/// # }); /// # });

View File

@ -1819,8 +1819,7 @@ impl BaseClient {
if let Some(olm) = olm.as_ref() { if let Some(olm) = olm.as_ref() {
olm.get_device(user_id, device_id).await olm.get_device(user_id, device_id).await
} else { } else {
// TODO remove this panic. Ok(None)
panic!("The client hasn't been logged in")
} }
} }

View File

@ -14,10 +14,6 @@
// TODO // 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 // 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. // session send out the key if we trust the device.
// //
@ -31,7 +27,7 @@ use serde::{Deserialize, Serialize};
use serde_json::{value::to_raw_value, Value}; use serde_json::{value::to_raw_value, Value};
use std::{collections::BTreeMap, convert::TryInto, ops::Deref, sync::Arc}; use std::{collections::BTreeMap, convert::TryInto, ops::Deref, sync::Arc};
use thiserror::Error; use thiserror::Error;
use tracing::{info, instrument, trace, warn}; use tracing::{error, info, instrument, trace, warn};
use matrix_sdk_common::{ use matrix_sdk_common::{
api::r0::to_device::DeviceIdOrAllDevices, api::r0::to_device::DeviceIdOrAllDevices,
@ -62,12 +58,37 @@ struct Device {
} }
impl Device { impl Device {
/// Encrypt the given inbound group session as a forwarded room key for this
/// device.
pub async fn encrypt_session(
&self,
session: InboundGroupSession,
) -> OlmResult<EncryptedEventContent> {
let export = session.export().await;
let content: ForwardedRoomKeyEventContent = if let Ok(c) = export.try_into() {
c
} else {
// TODO remove this panic.
panic!(
"Can't share session {} with device {} {}, key export can't \
be converted to a forwarded room key content",
session.session_id(),
self.user_id(),
self.device_id()
);
};
let content = serde_json::to_value(content)?;
self.encrypt(EventType::ForwardedRoomKey, content).await
}
fn trust_state(&self) -> bool { fn trust_state(&self) -> bool {
self.inner self.inner
.trust_state(&self.own_identity, &self.device_owner_identity) .trust_state(&self.own_identity, &self.device_owner_identity)
} }
pub(crate) async fn encrypt( async fn encrypt(
&self, &self,
event_type: EventType, event_type: EventType,
content: Value, content: Value,
@ -208,7 +229,7 @@ impl KeyRequestMachine {
/// Handle all the incoming key requests that are queued up and empty our /// Handle all the incoming key requests that are queued up and empty our
/// key request queue. /// key request queue.
pub async fn collect_incoming_key_requests(&self) -> Result<(), CryptoStoreError> { pub async fn collect_incoming_key_requests(&self) -> OlmResult<()> {
for item in self.incoming_key_requests.iter() { for item in self.incoming_key_requests.iter() {
let event = item.value(); let event = item.value();
self.handle_key_request(event).await?; self.handle_key_request(event).await?;
@ -224,7 +245,7 @@ impl KeyRequestMachine {
async fn handle_key_request( async fn handle_key_request(
&self, &self,
event: &ToDeviceEvent<RoomKeyRequestEventContent>, event: &ToDeviceEvent<RoomKeyRequestEventContent>,
) -> Result<(), CryptoStoreError> { ) -> OlmResult<()> {
let key_info = match event.content.action { let key_info = match event.content.action {
Action::Request => { Action::Request => {
if let Some(info) = &event.content.body { if let Some(info) = &event.content.body {
@ -273,7 +294,6 @@ impl KeyRequestMachine {
}); });
if let Some(device) = device { if let Some(device) = device {
// TODO get the matching outbound session.
if let Err(e) = self.should_share_session( if let Err(e) = self.should_share_session(
&device, &device,
self.outbound_group_sessions self.outbound_group_sessions
@ -294,7 +314,8 @@ impl KeyRequestMachine {
device.device_id() device.device_id()
); );
self.share_session(session, device).await; // TODO the missing session error here.
self.share_session(session, device).await?;
} }
} else { } else {
warn!( warn!(
@ -307,17 +328,10 @@ impl KeyRequestMachine {
Ok(()) Ok(())
} }
async fn share_session(&self, session: InboundGroupSession, device: Device) { async fn share_session(&self, session: InboundGroupSession, device: Device) -> OlmResult<()> {
let export = session.export().await; let content = device.encrypt_session(session).await?;
let content: ForwardedRoomKeyEventContent = export.try_into().unwrap();
let content = serde_json::to_value(content).unwrap();
let content = device
.encrypt(EventType::ForwardedRoomKey, content)
.await
.unwrap();
let id = Uuid::new_v4(); let id = Uuid::new_v4();
let mut messages = BTreeMap::new(); let mut messages = BTreeMap::new();
messages messages
@ -325,7 +339,7 @@ impl KeyRequestMachine {
.or_insert_with(BTreeMap::new) .or_insert_with(BTreeMap::new)
.insert( .insert(
DeviceIdOrAllDevices::DeviceId(device.device_id().into()), DeviceIdOrAllDevices::DeviceId(device.device_id().into()),
to_raw_value(&content).unwrap(), to_raw_value(&content)?,
); );
let request = OutgoingRequest { let request = OutgoingRequest {
@ -341,6 +355,8 @@ impl KeyRequestMachine {
}; };
self.outgoing_to_device_requests.insert(id, request); self.outgoing_to_device_requests.insert(id, request);
Ok(())
} }
/// Check if it's ok to share a session with the given device. /// Check if it's ok to share a session with the given device.
@ -590,7 +606,7 @@ mod test {
events::{ events::{
forwarded_room_key::ForwardedRoomKeyEventContent, forwarded_room_key::ForwardedRoomKeyEventContent,
room::encrypted::EncryptedEventContent, room_key_request::RoomKeyRequestEventContent, room::encrypted::EncryptedEventContent, room_key_request::RoomKeyRequestEventContent,
ToDeviceEvent, AnyToDeviceEvent, ToDeviceEvent,
}, },
identifiers::{room_id, user_id, DeviceIdBox, RoomId, UserId}, identifiers::{room_id, user_id, DeviceIdBox, RoomId, UserId},
}; };
@ -599,7 +615,7 @@ mod test {
use crate::{ use crate::{
identities::{LocalTrust, ReadOnlyDevice}, identities::{LocalTrust, ReadOnlyDevice},
olm::ReadOnlyAccount, olm::{Account, ReadOnlyAccount},
store::{MemoryStore, Store}, store::{MemoryStore, Store},
}; };
@ -905,7 +921,10 @@ mod test {
#[async_test] #[async_test]
async fn key_share_cycle() { async fn key_share_cycle() {
let alice_machine = get_machine(); let alice_machine = get_machine();
let alice_account = account(); let alice_account = Account {
inner: account(),
store: alice_machine.store.clone(),
};
let bob_machine = bob_machine(); let bob_machine = bob_machine();
let bob_account = bob_account(); let bob_account = bob_account();
@ -964,7 +983,7 @@ mod test {
// Put the outbound session into bobs store. // Put the outbound session into bobs store.
bob_machine bob_machine
.outbound_group_sessions .outbound_group_sessions
.insert(room_id(), group_session); .insert(room_id(), group_session.clone());
// Get the request and convert it into a event. // Get the request and convert it into a event.
let request = alice_machine let request = alice_machine
@ -1029,12 +1048,45 @@ mod test {
.await .await
.unwrap(); .unwrap();
let _event = ToDeviceEvent { let mut event = ToDeviceEvent {
sender: bob_id(), sender: bob_id(),
content, content,
}; };
// TODO test that alice can receive, decrypt and add the requested key // Check that alice doesn't have the session.
// to the store. assert!(alice_machine
.store
.get_inbound_group_session(
&room_id(),
&bob_account.identity_keys().curve25519(),
group_session.session_id()
)
.await
.unwrap()
.is_none());
let (decrypted, sender_key, _) = alice_account
.decrypt_to_device_event(&mut event)
.await
.unwrap();
if let AnyToDeviceEvent::ForwardedRoomKey(mut e) = decrypted.deserialize().unwrap() {
alice_machine
.receive_forwarded_room_key(&sender_key, &mut e)
.await
.unwrap();
} else {
panic!("Invalid decrypted event type");
}
// Check that alice now does have the session.
let session = alice_machine
.store
.get_inbound_group_session(&room_id(), &sender_key, group_session.session_id())
.await
.unwrap()
.unwrap();
assert_eq!(session.session_id(), group_session.session_id())
} }
} }

View File

@ -14,7 +14,7 @@
#[cfg(feature = "sqlite_cryptostore")] #[cfg(feature = "sqlite_cryptostore")]
use std::path::Path; use std::path::Path;
use std::{collections::BTreeMap, convert::TryInto, mem, sync::Arc, time::Duration}; use std::{collections::BTreeMap, mem, sync::Arc, time::Duration};
use dashmap::DashMap; use dashmap::DashMap;
use tracing::{debug, error, info, instrument, trace, warn}; use tracing::{debug, error, info, instrument, trace, warn};
@ -51,7 +51,7 @@ use super::{
key_request::KeyRequestMachine, key_request::KeyRequestMachine,
olm::{ olm::{
Account, EncryptionSettings, ExportedRoomKey, GroupSessionKey, IdentityKeys, Account, EncryptionSettings, ExportedRoomKey, GroupSessionKey, IdentityKeys,
InboundGroupSession, OlmMessage, OutboundGroupSession, ReadOnlyAccount, InboundGroupSession, OutboundGroupSession, ReadOnlyAccount,
}, },
requests::{IncomingResponse, OutgoingRequest, ToDeviceRequest}, requests::{IncomingResponse, OutgoingRequest, ToDeviceRequest},
store::{CryptoStore, MemoryStore, Result as StoreResult, Store}, store::{CryptoStore, MemoryStore, Result as StoreResult, Store},
@ -513,61 +513,22 @@ impl OlmMachine {
&self, &self,
event: &ToDeviceEvent<EncryptedEventContent>, event: &ToDeviceEvent<EncryptedEventContent>,
) -> OlmResult<Raw<AnyToDeviceEvent>> { ) -> OlmResult<Raw<AnyToDeviceEvent>> {
info!("Decrypting to-device event"); let (decrypted_event, sender_key, signing_key) =
self.account.decrypt_to_device_event(event).await?;
let content = if let EncryptedEventContent::OlmV1Curve25519AesSha2(c) = &event.content { // Handle the decrypted event, e.g. fetch out Megolm sessions out of
c // the event.
if let Some(event) = self
.handle_decrypted_to_device_event(&sender_key, &signing_key, &decrypted_event)
.await?
{
// Some events may have sensitive data e.g. private keys, while we
// want to notify our users that a private key was received we
// don't want them to be able to do silly things with it. Handling
// events modifies them and returns a modified one, so replace it
// here if we get one.
Ok(event)
} else { } else {
warn!("Error, unsupported encryption algorithm"); Ok(decrypted_event)
return Err(EventError::UnsupportedAlgorithm.into());
};
let identity_keys = self.account.identity_keys();
let own_key = identity_keys.curve25519();
let own_ciphertext = content.ciphertext.get(own_key);
// Try to find a ciphertext that was meant for our device.
if let Some(ciphertext) = own_ciphertext {
let message_type: u8 = ciphertext
.message_type
.try_into()
.map_err(|_| EventError::UnsupportedOlmType)?;
// Create a OlmMessage from the ciphertext and the type.
let message =
OlmMessage::from_type_and_ciphertext(message_type.into(), ciphertext.body.clone())
.map_err(|_| EventError::UnsupportedOlmType)?;
// Decrypt the OlmMessage and get a Ruma event out of it.
let (decrypted_event, signing_key) = self
.account
.decrypt_olm_message(&event.sender, &content.sender_key, message)
.await?;
debug!("Decrypted a to-device event {:?}", decrypted_event);
// Handle the decrypted event, e.g. fetch out Megolm sessions out of
// the event.
if let Some(event) = self
.handle_decrypted_to_device_event(
&content.sender_key,
&signing_key,
&decrypted_event,
)
.await?
{
// Some events may have sensitive data e.g. private keys, while we
// want to notify our users that a private key was received we
// don't want them to be able to do silly things with it. Handling
// events modifies them and returns a modified one, so replace it
// here if we get one.
Ok(event)
} else {
Ok(decrypted_event)
}
} else {
warn!("Olm event doesn't contain a ciphertext for our key");
Err(EventError::MissingCiphertext.into())
} }
} }

View File

@ -12,6 +12,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
use matrix_sdk_common::events::ToDeviceEvent;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_json::{json, Value}; use serde_json::{json, Value};
use std::{ use std::{
@ -27,11 +28,11 @@ use std::{
use tracing::{debug, trace, warn}; use tracing::{debug, trace, warn};
#[cfg(test)] #[cfg(test)]
use matrix_sdk_common::events::{room::encrypted::EncryptedEventContent, EventType}; use matrix_sdk_common::events::EventType;
use matrix_sdk_common::{ use matrix_sdk_common::{
api::r0::keys::{upload_keys, OneTimeKey, SignedKey}, api::r0::keys::{upload_keys, OneTimeKey, SignedKey},
encryption::DeviceKeys, encryption::DeviceKeys,
events::AnyToDeviceEvent, events::{room::encrypted::EncryptedEventContent, AnyToDeviceEvent},
identifiers::{ identifiers::{
DeviceId, DeviceIdBox, DeviceKeyAlgorithm, DeviceKeyId, EventEncryptionAlgorithm, RoomId, DeviceId, DeviceIdBox, DeviceKeyAlgorithm, DeviceKeyId, EventEncryptionAlgorithm, RoomId,
UserId, UserId,
@ -72,6 +73,48 @@ impl Deref for Account {
} }
impl Account { impl Account {
pub async fn decrypt_to_device_event(
&self,
event: &ToDeviceEvent<EncryptedEventContent>,
) -> OlmResult<(Raw<AnyToDeviceEvent>, String, String)> {
debug!("Decrypting to-device event");
let content = if let EncryptedEventContent::OlmV1Curve25519AesSha2(c) = &event.content {
c
} else {
warn!("Error, unsupported encryption algorithm");
return Err(EventError::UnsupportedAlgorithm.into());
};
let identity_keys = self.inner.identity_keys();
let own_key = identity_keys.curve25519();
let own_ciphertext = content.ciphertext.get(own_key);
// Try to find a ciphertext that was meant for our device.
if let Some(ciphertext) = own_ciphertext {
let message_type: u8 = ciphertext
.message_type
.try_into()
.map_err(|_| EventError::UnsupportedOlmType)?;
// Create a OlmMessage from the ciphertext and the type.
let message =
OlmMessage::from_type_and_ciphertext(message_type.into(), ciphertext.body.clone())
.map_err(|_| EventError::UnsupportedOlmType)?;
// Decrypt the OlmMessage and get a Ruma event out of it.
let (decrypted_event, signing_key) = self
.decrypt_olm_message(&event.sender, &content.sender_key, message)
.await?;
debug!("Decrypted a to-device event {:?}", decrypted_event);
Ok((decrypted_event, content.sender_key.clone(), signing_key))
} else {
warn!("Olm event doesn't contain a ciphertext for our key");
Err(EventError::MissingCiphertext.into())
}
}
pub async fn update_uploaded_key_count( pub async fn update_uploaded_key_count(
&self, &self,
key_count: &BTreeMap<DeviceKeyAlgorithm, UInt>, key_count: &BTreeMap<DeviceKeyAlgorithm, UInt>,
@ -146,21 +189,24 @@ impl Account {
let ret = session.decrypt(message.clone()).await; let ret = session.decrypt(message.clone()).await;
if let Ok(p) = ret { match ret {
plaintext = Some(p); Ok(p) => {
session_to_save = Some(session.clone()); plaintext = Some(p);
session_to_save = Some(session.clone());
break; break;
} else { }
// Decryption failed with a matching session, the session is Err(e) => {
// likely wedged and needs to be rotated. // Decryption failed with a matching session, the session is
if matches { // likely wedged and needs to be rotated.
warn!( if matches {
"Found a matching Olm session yet decryption failed warn!(
for sender {} and sender_key {}", "Found a matching Olm session yet decryption failed
sender, sender_key for sender {} and sender_key {} {:?}",
); sender, sender_key, e
return Err(OlmError::SessionWedged); );
return Err(OlmError::SessionWedged);
}
} }
} }
} }
@ -176,7 +222,7 @@ impl Account {
} }
/// Decrypt an Olm message, creating a new Olm session if possible. /// Decrypt an Olm message, creating a new Olm session if possible.
pub async fn decrypt_olm_message( async fn decrypt_olm_message(
&self, &self,
sender: &UserId, sender: &UserId,
sender_key: &str, sender_key: &str,
@ -882,6 +928,8 @@ impl ReadOnlyAccount {
.await .await
.unwrap(); .unwrap();
other.mark_keys_as_published().await;
let message = our_session let message = our_session
.encrypt(&device, EventType::Dummy, json!({})) .encrypt(&device, EventType::Dummy, json!({}))
.await .await
@ -901,14 +949,14 @@ impl ReadOnlyAccount {
let message = let message =
OlmMessage::from_type_and_ciphertext(message_type.into(), own_ciphertext.body.clone()) OlmMessage::from_type_and_ciphertext(message_type.into(), own_ciphertext.body.clone())
.unwrap(); .unwrap();
let message = if let OlmMessage::PreKey(m) = message { let prekey = if let OlmMessage::PreKey(m) = message.clone() {
m m
} else { } else {
panic!("Wrong Olm message type"); panic!("Wrong Olm message type");
}; };
let our_device = ReadOnlyDevice::from_account(self).await; let our_device = ReadOnlyDevice::from_account(self).await;
let other_session = other let mut other_session = other
.create_inbound_session( .create_inbound_session(
our_device our_device
.keys() .keys()
@ -917,11 +965,13 @@ impl ReadOnlyAccount {
our_device.device_id(), our_device.device_id(),
)) ))
.unwrap(), .unwrap(),
message, prekey,
) )
.await .await
.unwrap(); .unwrap();
other_session.decrypt(message).await.unwrap();
(our_session, other_session) (our_session, other_session)
} }
} }

View File

@ -30,7 +30,6 @@ pub use group_sessions::{
}; };
pub(crate) use group_sessions::{GroupSessionKey, OutboundGroupSession}; pub(crate) use group_sessions::{GroupSessionKey, OutboundGroupSession};
pub use olm_rs::{account::IdentityKeys, PicklingMode}; pub use olm_rs::{account::IdentityKeys, PicklingMode};
pub(crate) use session::OlmMessage;
pub use session::{PickledSession, Session, SessionPickle}; pub use session::{PickledSession, Session, SessionPickle};
pub(crate) use utility::Utility; pub(crate) use utility::Utility;