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 client = Client::new(homeserver).unwrap();
/// # 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());
///

View File

@ -55,7 +55,10 @@ impl Device {
/// # let homeserver = Url::parse("http://example.com").unwrap();
/// # let client = Client::new(homeserver).unwrap();
/// # 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();
/// # });

View File

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

View File

@ -14,10 +14,6 @@
// 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.
//
@ -31,7 +27,7 @@ use serde::{Deserialize, Serialize};
use serde_json::{value::to_raw_value, Value};
use std::{collections::BTreeMap, convert::TryInto, ops::Deref, sync::Arc};
use thiserror::Error;
use tracing::{info, instrument, trace, warn};
use tracing::{error, info, instrument, trace, warn};
use matrix_sdk_common::{
api::r0::to_device::DeviceIdOrAllDevices,
@ -62,12 +58,37 @@ struct 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 {
self.inner
.trust_state(&self.own_identity, &self.device_owner_identity)
}
pub(crate) async fn encrypt(
async fn encrypt(
&self,
event_type: EventType,
content: Value,
@ -208,7 +229,7 @@ impl KeyRequestMachine {
/// Handle all the incoming key requests that are queued up and empty our
/// 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() {
let event = item.value();
self.handle_key_request(event).await?;
@ -224,7 +245,7 @@ impl KeyRequestMachine {
async fn handle_key_request(
&self,
event: &ToDeviceEvent<RoomKeyRequestEventContent>,
) -> Result<(), CryptoStoreError> {
) -> OlmResult<()> {
let key_info = match event.content.action {
Action::Request => {
if let Some(info) = &event.content.body {
@ -273,7 +294,6 @@ impl KeyRequestMachine {
});
if let Some(device) = device {
// TODO get the matching outbound session.
if let Err(e) = self.should_share_session(
&device,
self.outbound_group_sessions
@ -294,7 +314,8 @@ impl KeyRequestMachine {
device.device_id()
);
self.share_session(session, device).await;
// TODO the missing session error here.
self.share_session(session, device).await?;
}
} else {
warn!(
@ -307,17 +328,10 @@ impl KeyRequestMachine {
Ok(())
}
async fn share_session(&self, session: InboundGroupSession, device: Device) {
let export = session.export().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();
async fn share_session(&self, session: InboundGroupSession, device: Device) -> OlmResult<()> {
let content = device.encrypt_session(session).await?;
let id = Uuid::new_v4();
let mut messages = BTreeMap::new();
messages
@ -325,7 +339,7 @@ impl KeyRequestMachine {
.or_insert_with(BTreeMap::new)
.insert(
DeviceIdOrAllDevices::DeviceId(device.device_id().into()),
to_raw_value(&content).unwrap(),
to_raw_value(&content)?,
);
let request = OutgoingRequest {
@ -341,6 +355,8 @@ impl KeyRequestMachine {
};
self.outgoing_to_device_requests.insert(id, request);
Ok(())
}
/// Check if it's ok to share a session with the given device.
@ -590,7 +606,7 @@ mod test {
events::{
forwarded_room_key::ForwardedRoomKeyEventContent,
room::encrypted::EncryptedEventContent, room_key_request::RoomKeyRequestEventContent,
ToDeviceEvent,
AnyToDeviceEvent, ToDeviceEvent,
},
identifiers::{room_id, user_id, DeviceIdBox, RoomId, UserId},
};
@ -599,7 +615,7 @@ mod test {
use crate::{
identities::{LocalTrust, ReadOnlyDevice},
olm::ReadOnlyAccount,
olm::{Account, ReadOnlyAccount},
store::{MemoryStore, Store},
};
@ -905,7 +921,10 @@ mod test {
#[async_test]
async fn key_share_cycle() {
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_account = bob_account();
@ -964,7 +983,7 @@ mod test {
// Put the outbound session into bobs store.
bob_machine
.outbound_group_sessions
.insert(room_id(), group_session);
.insert(room_id(), group_session.clone());
// Get the request and convert it into a event.
let request = alice_machine
@ -1029,12 +1048,45 @@ mod test {
.await
.unwrap();
let _event = ToDeviceEvent {
let mut event = ToDeviceEvent {
sender: bob_id(),
content,
};
// TODO test that alice can receive, decrypt and add the requested key
// to the store.
// Check that alice doesn't have the session.
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")]
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 tracing::{debug, error, info, instrument, trace, warn};
@ -51,7 +51,7 @@ use super::{
key_request::KeyRequestMachine,
olm::{
Account, EncryptionSettings, ExportedRoomKey, GroupSessionKey, IdentityKeys,
InboundGroupSession, OlmMessage, OutboundGroupSession, ReadOnlyAccount,
InboundGroupSession, OutboundGroupSession, ReadOnlyAccount,
},
requests::{IncomingResponse, OutgoingRequest, ToDeviceRequest},
store::{CryptoStore, MemoryStore, Result as StoreResult, Store},
@ -513,61 +513,22 @@ impl OlmMachine {
&self,
event: &ToDeviceEvent<EncryptedEventContent>,
) -> OlmResult<Raw<AnyToDeviceEvent>> {
info!("Decrypting to-device event");
let content = if let EncryptedEventContent::OlmV1Curve25519AesSha2(c) = &event.content {
c
let (decrypted_event, sender_key, signing_key) =
self.account.decrypt_to_device_event(event).await?;
// Handle the decrypted event, e.g. fetch out Megolm sessions out of
// 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 {
warn!("Error, unsupported encryption algorithm");
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())
Ok(decrypted_event)
}
}

View File

@ -12,6 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
use matrix_sdk_common::events::ToDeviceEvent;
use serde::{Deserialize, Serialize};
use serde_json::{json, Value};
use std::{
@ -27,11 +28,11 @@ use std::{
use tracing::{debug, trace, warn};
#[cfg(test)]
use matrix_sdk_common::events::{room::encrypted::EncryptedEventContent, EventType};
use matrix_sdk_common::events::EventType;
use matrix_sdk_common::{
api::r0::keys::{upload_keys, OneTimeKey, SignedKey},
encryption::DeviceKeys,
events::AnyToDeviceEvent,
events::{room::encrypted::EncryptedEventContent, AnyToDeviceEvent},
identifiers::{
DeviceId, DeviceIdBox, DeviceKeyAlgorithm, DeviceKeyId, EventEncryptionAlgorithm, RoomId,
UserId,
@ -72,6 +73,48 @@ impl Deref for 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(
&self,
key_count: &BTreeMap<DeviceKeyAlgorithm, UInt>,
@ -146,21 +189,24 @@ impl Account {
let ret = session.decrypt(message.clone()).await;
if let Ok(p) = ret {
plaintext = Some(p);
session_to_save = Some(session.clone());
match ret {
Ok(p) => {
plaintext = Some(p);
session_to_save = Some(session.clone());
break;
} else {
// Decryption failed with a matching session, the session is
// likely wedged and needs to be rotated.
if matches {
warn!(
"Found a matching Olm session yet decryption failed
for sender {} and sender_key {}",
sender, sender_key
);
return Err(OlmError::SessionWedged);
break;
}
Err(e) => {
// Decryption failed with a matching session, the session is
// likely wedged and needs to be rotated.
if matches {
warn!(
"Found a matching Olm session yet decryption failed
for sender {} and sender_key {} {:?}",
sender, sender_key, e
);
return Err(OlmError::SessionWedged);
}
}
}
}
@ -176,7 +222,7 @@ impl Account {
}
/// Decrypt an Olm message, creating a new Olm session if possible.
pub async fn decrypt_olm_message(
async fn decrypt_olm_message(
&self,
sender: &UserId,
sender_key: &str,
@ -882,6 +928,8 @@ impl ReadOnlyAccount {
.await
.unwrap();
other.mark_keys_as_published().await;
let message = our_session
.encrypt(&device, EventType::Dummy, json!({}))
.await
@ -901,14 +949,14 @@ impl ReadOnlyAccount {
let message =
OlmMessage::from_type_and_ciphertext(message_type.into(), own_ciphertext.body.clone())
.unwrap();
let message = if let OlmMessage::PreKey(m) = message {
let prekey = if let OlmMessage::PreKey(m) = message.clone() {
m
} else {
panic!("Wrong Olm message type");
};
let our_device = ReadOnlyDevice::from_account(self).await;
let other_session = other
let mut other_session = other
.create_inbound_session(
our_device
.keys()
@ -917,11 +965,13 @@ impl ReadOnlyAccount {
our_device.device_id(),
))
.unwrap(),
message,
prekey,
)
.await
.unwrap();
other_session.decrypt(message).await.unwrap();
(our_session, other_session)
}
}

View File

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