crypto: Test the full key share flow.
parent
84066d4a76
commit
8fe1eda169
|
@ -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());
|
||||
///
|
||||
|
|
|
@ -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();
|
||||
/// # });
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,47 +513,12 @@ impl OlmMachine {
|
|||
&self,
|
||||
event: &ToDeviceEvent<EncryptedEventContent>,
|
||||
) -> OlmResult<Raw<AnyToDeviceEvent>> {
|
||||
info!("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.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);
|
||||
|
||||
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(
|
||||
&content.sender_key,
|
||||
&signing_key,
|
||||
&decrypted_event,
|
||||
)
|
||||
.handle_decrypted_to_device_event(&sender_key, &signing_key, &decrypted_event)
|
||||
.await?
|
||||
{
|
||||
// Some events may have sensitive data e.g. private keys, while we
|
||||
|
@ -565,10 +530,6 @@ impl OlmMachine {
|
|||
} else {
|
||||
Ok(decrypted_event)
|
||||
}
|
||||
} else {
|
||||
warn!("Olm event doesn't contain a ciphertext for our key");
|
||||
Err(EventError::MissingCiphertext.into())
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a group session from a room key and add it to our crypto store.
|
||||
|
|
|
@ -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,24 +189,27 @@ impl Account {
|
|||
|
||||
let ret = session.decrypt(message.clone()).await;
|
||||
|
||||
if let Ok(p) = ret {
|
||||
match ret {
|
||||
Ok(p) => {
|
||||
plaintext = Some(p);
|
||||
session_to_save = Some(session.clone());
|
||||
|
||||
break;
|
||||
} else {
|
||||
}
|
||||
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
|
||||
for sender {} and sender_key {} {:?}",
|
||||
sender, sender_key, e
|
||||
);
|
||||
return Err(OlmError::SessionWedged);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(session) = session_to_save {
|
||||
// Decryption was successful, save the new ratchet state of the
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
Loading…
Reference in New Issue