Skip to content

Commit d41af39

Browse files
authored
Embed device keys in Olm-encrypted messages (#3517)
This patch implements MSC4147[1]. Signed-off-by: Hubert Chathi <[email protected]> [1]: matrix-org/matrix-spec-proposals#4147
1 parent 37c125c commit d41af39

File tree

14 files changed

+393
-84
lines changed

14 files changed

+393
-84
lines changed

bindings/matrix-sdk-crypto-ffi/src/lib.rs

Lines changed: 36 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,11 @@ mod responses;
1616
mod users;
1717
mod verification;
1818

19-
use std::{collections::HashMap, sync::Arc, time::Duration};
19+
use std::{
20+
collections::{BTreeMap, HashMap},
21+
sync::Arc,
22+
time::Duration,
23+
};
2024

2125
use anyhow::Context as _;
2226
pub use backup_recovery_key::{
@@ -33,7 +37,9 @@ use matrix_sdk_common::deserialized_responses::ShieldState as RustShieldState;
3337
use matrix_sdk_crypto::{
3438
olm::{IdentityKeys, InboundGroupSession, Session},
3539
store::{Changes, CryptoStore, PendingChanges, RoomSettings as RustRoomSettings},
36-
types::{EventEncryptionAlgorithm as RustEventEncryptionAlgorithm, SigningKey},
40+
types::{
41+
DeviceKey, DeviceKeys, EventEncryptionAlgorithm as RustEventEncryptionAlgorithm, SigningKey,
42+
},
3743
CollectStrategy, EncryptionSettings as RustEncryptionSettings,
3844
};
3945
use matrix_sdk_sqlite::SqliteCryptoStore;
@@ -43,8 +49,8 @@ pub use responses::{
4349
};
4450
use ruma::{
4551
events::room::history_visibility::HistoryVisibility as RustHistoryVisibility,
46-
DeviceKeyAlgorithm, MilliSecondsSinceUnixEpoch, OwnedDeviceId, OwnedUserId, RoomId,
47-
SecondsSinceUnixEpoch, UserId,
52+
DeviceKeyAlgorithm, DeviceKeyId, MilliSecondsSinceUnixEpoch, OwnedDeviceId, OwnedUserId,
53+
RoomId, SecondsSinceUnixEpoch, UserId,
4854
};
4955
use serde::{Deserialize, Serialize};
5056
use tokio::runtime::Runtime;
@@ -332,6 +338,10 @@ async fn save_changes(
332338
processed_steps += 1;
333339
listener(processed_steps, total_steps);
334340

341+
// The Sessions were created with incorrect device keys, so clear the cache
342+
// so that they'll get recreated with correct ones.
343+
store.clear_caches().await;
344+
335345
Ok(())
336346
}
337347

@@ -419,6 +429,27 @@ fn collect_sessions(
419429
) -> anyhow::Result<(Vec<Session>, Vec<InboundGroupSession>)> {
420430
let mut sessions = Vec::new();
421431

432+
// Create a DeviceKeys struct with enough information to get a working
433+
// Session, but we will won't actually use the Sessions (and we'll clear
434+
// the session cache after migration) so we don't need to worry about
435+
// signatures.
436+
let device_keys = DeviceKeys::new(
437+
user_id.clone(),
438+
device_id.clone(),
439+
Default::default(),
440+
BTreeMap::from([
441+
(
442+
DeviceKeyId::from_parts(DeviceKeyAlgorithm::Ed25519, &device_id),
443+
DeviceKey::Ed25519(identity_keys.ed25519),
444+
),
445+
(
446+
DeviceKeyId::from_parts(DeviceKeyAlgorithm::Curve25519, &device_id),
447+
DeviceKey::Curve25519(identity_keys.curve25519),
448+
),
449+
]),
450+
Default::default(),
451+
);
452+
422453
for session_pickle in session_pickles {
423454
let pickle =
424455
vodozemac::olm::Session::from_libolm_pickle(&session_pickle.pickle, pickle_key)?
@@ -439,8 +470,7 @@ fn collect_sessions(
439470
last_use_time,
440471
};
441472

442-
let session =
443-
Session::from_pickle(user_id.clone(), device_id.clone(), identity_keys.clone(), pickle);
473+
let session = Session::from_pickle(device_keys.clone(), pickle)?;
444474

445475
sessions.push(session);
446476
processed_steps += 1;

crates/matrix-sdk-crypto/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ testing = ["dep:http"]
3030
[dependencies]
3131
aes = "0.8.1"
3232
as_variant = { workspace = true }
33+
assert_matches2 = { workspace = true }
3334
async-trait = { workspace = true }
3435
bs58 = { version = "0.5.0" }
3536
byteorder = { workspace = true }

crates/matrix-sdk-crypto/src/error.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,19 @@ pub enum EventError {
165165
MismatchedRoom(OwnedRoomId, Option<OwnedRoomId>),
166166
}
167167

168+
/// Error type describing different errors that can happen when we create an
169+
/// Olm session from a pickle.
170+
#[derive(Error, Debug)]
171+
pub enum SessionUnpickleError {
172+
/// The device keys are missing the signing key
173+
#[error("the device keys are missing the signing key")]
174+
MissingSigningKey,
175+
176+
/// The device keys are missing the identity key
177+
#[error("the device keys are missing the identity key")]
178+
MissingIdentityKey,
179+
}
180+
168181
/// Error type describing different errors that happen when we check or create
169182
/// signatures for a Matrix JSON object.
170183
#[derive(Error, Debug)]

crates/matrix-sdk-crypto/src/identities/device.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -871,7 +871,8 @@ impl ReadOnlyDevice {
871871
}
872872
}
873873

874-
pub(crate) fn as_device_keys(&self) -> &DeviceKeys {
874+
/// Return the device keys
875+
pub fn as_device_keys(&self) -> &DeviceKeys {
875876
&self.inner
876877
}
877878

crates/matrix-sdk-crypto/src/machine.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,14 @@ impl OlmMachine {
173173
let static_account = account.static_data().clone();
174174

175175
let store = Arc::new(CryptoStoreWrapper::new(self.user_id(), MemoryStore::new()));
176+
let device = ReadOnlyDevice::from_account(&account);
176177
store.save_pending_changes(PendingChanges { account: Some(account) }).await?;
178+
store
179+
.save_changes(Changes {
180+
devices: DeviceChanges { new: vec![device], ..Default::default() },
181+
..Default::default()
182+
})
183+
.await?;
177184

178185
Ok(Self::new_helper(
179186
device_id,

crates/matrix-sdk-crypto/src/olm/account.rs

Lines changed: 36 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -905,25 +905,27 @@ impl Account {
905905
/// and shared with us.
906906
///
907907
/// * `fallback_used` - Was the one-time key a fallback key.
908+
///
909+
/// * `our_device_keys` - Our own `DeviceKeys`, including cross-signing
910+
/// signatures if applicable, for embedding in encrypted messages.
908911
pub fn create_outbound_session_helper(
909912
&self,
910913
config: SessionConfig,
911914
identity_key: Curve25519PublicKey,
912915
one_time_key: Curve25519PublicKey,
913916
fallback_used: bool,
917+
our_device_keys: DeviceKeys,
914918
) -> Session {
915919
let session = self.inner.create_outbound_session(config, identity_key, one_time_key);
916920

917921
let now = SecondsSinceUnixEpoch::now();
918922
let session_id = session.session_id();
919923

920924
Session {
921-
user_id: self.static_data.user_id.clone(),
922-
device_id: self.static_data.device_id.clone(),
923-
our_identity_keys: self.static_data.identity_keys.clone(),
924925
inner: Arc::new(Mutex::new(session)),
925926
session_id: session_id.into(),
926927
sender_key: identity_key,
928+
our_device_keys,
927929
created_using_fallback_key: fallback_used,
928930
creation_time: now,
929931
last_use_time: now,
@@ -978,11 +980,15 @@ impl Account {
978980
///
979981
/// * `key_map` - A map from the algorithm and device ID to the one-time key
980982
/// that the other account created and shared with us.
983+
///
984+
/// * `our_device_keys` - Our own `DeviceKeys`, including cross-signing
985+
/// signatures if applicable, for embedding in encrypted messages.
981986
#[allow(clippy::result_large_err)]
982987
pub fn create_outbound_session(
983988
&self,
984989
device: &ReadOnlyDevice,
985990
key_map: &BTreeMap<OwnedDeviceKeyId, Raw<ruma::encryption::OneTimeKey>>,
991+
our_device_keys: DeviceKeys,
986992
) -> Result<Session, SessionCreationError> {
987993
let pre_key_bundle = Self::find_pre_key_bundle(device, key_map)?;
988994

@@ -1012,6 +1018,7 @@ impl Account {
10121018
identity_key,
10131019
one_time_key,
10141020
is_fallback,
1021+
our_device_keys,
10151022
))
10161023
}
10171024
}
@@ -1026,11 +1033,15 @@ impl Account {
10261033
///
10271034
/// * `their_identity_key` - The other account's identity/curve25519 key.
10281035
///
1036+
/// * `our_device_keys` - Our own `DeviceKeys`, including cross-signing
1037+
/// signatures if applicable, for embedding in encrypted messages.
1038+
///
10291039
/// * `message` - A pre-key Olm message that was sent to us by the other
10301040
/// account.
10311041
pub fn create_inbound_session(
10321042
&mut self,
10331043
their_identity_key: Curve25519PublicKey,
1044+
our_device_keys: DeviceKeys,
10341045
message: &PreKeyMessage,
10351046
) -> Result<InboundCreationResult, SessionCreationError> {
10361047
Span::current().record("session_id", debug(message.session_id()));
@@ -1043,12 +1054,10 @@ impl Account {
10431054
debug!(session=?result.session, "Decrypted an Olm message from a new Olm session");
10441055

10451056
let session = Session {
1046-
user_id: self.static_data.user_id.clone(),
1047-
device_id: self.static_data.device_id.clone(),
1048-
our_identity_keys: self.static_data.identity_keys.clone(),
10491057
inner: Arc::new(Mutex::new(result.session)),
10501058
session_id: session_id.into(),
10511059
sender_key: their_identity_key,
1060+
our_device_keys,
10521061
created_using_fallback_key: false,
10531062
creation_time: now,
10541063
last_use_time: now,
@@ -1072,7 +1081,8 @@ impl Account {
10721081
let one_time_map = other.signed_one_time_keys();
10731082
let device = ReadOnlyDevice::from_account(other);
10741083

1075-
let mut our_session = self.create_outbound_session(&device, &one_time_map).unwrap();
1084+
let mut our_session =
1085+
self.create_outbound_session(&device, &one_time_map, self.device_keys()).unwrap();
10761086

10771087
other.mark_keys_as_published();
10781088

@@ -1104,8 +1114,13 @@ impl Account {
11041114
};
11051115

11061116
let our_device = ReadOnlyDevice::from_account(self);
1107-
let other_session =
1108-
other.create_inbound_session(our_device.curve25519_key().unwrap(), &prekey).unwrap();
1117+
let other_session = other
1118+
.create_inbound_session(
1119+
our_device.curve25519_key().unwrap(),
1120+
other.device_keys(),
1121+
&prekey,
1122+
)
1123+
.unwrap();
11091124

11101125
(our_session, other_session.session)
11111126
}
@@ -1290,20 +1305,23 @@ impl Account {
12901305
);
12911306

12921307
return Err(OlmError::SessionWedged(
1293-
session.user_id.to_owned(),
1308+
session.our_device_keys.user_id.to_owned(),
12941309
session.sender_key(),
12951310
));
12961311
}
12971312
}
12981313

1299-
// We didn't find a matching session; try to create a new session.
1300-
let result = match self.create_inbound_session(sender_key, prekey_message) {
1301-
Ok(r) => r,
1302-
Err(e) => {
1303-
warn!("Failed to create a new Olm session from a pre-key message: {e:?}");
1304-
return Err(OlmError::SessionWedged(sender.to_owned(), sender_key));
1305-
}
1306-
};
1314+
let device_keys = store.get_own_device().await?.as_device_keys().clone();
1315+
let result =
1316+
match self.create_inbound_session(sender_key, device_keys, prekey_message) {
1317+
Ok(r) => r,
1318+
Err(e) => {
1319+
warn!(
1320+
"Failed to create a new Olm session from a pre-key message: {e:?}"
1321+
);
1322+
return Err(OlmError::SessionWedged(sender.to_owned(), sender_key));
1323+
}
1324+
};
13071325

13081326
// We need to add the new session to the session cache, otherwise
13091327
// we might try to create the same session again.

crates/matrix-sdk-crypto/src/olm/mod.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ pub(crate) mod tests {
9393
sender_key,
9494
one_time_key,
9595
false,
96+
alice.device_keys(),
9697
);
9798

9899
(alice, session)
@@ -144,6 +145,7 @@ pub(crate) mod tests {
144145
alice_keys.curve25519,
145146
one_time_key,
146147
false,
148+
bob.device_keys(),
147149
);
148150

149151
let plaintext = "Hello world";
@@ -156,7 +158,9 @@ pub(crate) mod tests {
156158
};
157159

158160
let bob_keys = bob.identity_keys();
159-
let result = alice.create_inbound_session(bob_keys.curve25519, &prekey_message).unwrap();
161+
let result = alice
162+
.create_inbound_session(bob_keys.curve25519, alice.device_keys(), &prekey_message)
163+
.unwrap();
160164

161165
assert_eq!(bob_session.session_id(), result.session.session_id());
162166

0 commit comments

Comments
 (0)