@@ -53,7 +53,7 @@ use futures_util::StreamExt;
53
53
use matrix_sdk_common:: locks:: RwLock as StdRwLock ;
54
54
use ruma:: {
55
55
encryption:: KeyUsage , events:: secret:: request:: SecretName , DeviceId , OwnedDeviceId ,
56
- OwnedRoomId , OwnedUserId , UserId ,
56
+ OwnedRoomId , OwnedUserId , RoomId , UserId ,
57
57
} ;
58
58
use serde:: { de:: DeserializeOwned , Deserialize , Serialize } ;
59
59
use thiserror:: Error ;
@@ -94,10 +94,15 @@ pub mod integration_tests;
94
94
use caches:: { SequenceNumber , UsersForKeyQuery } ;
95
95
pub ( crate ) use crypto_store_wrapper:: CryptoStoreWrapper ;
96
96
pub use error:: { CryptoStoreError , Result } ;
97
- use matrix_sdk_common:: { store_locks:: CrossProcessStoreLock , timeout:: timeout} ;
97
+ use matrix_sdk_common:: {
98
+ deserialized_responses:: WithheldCode , store_locks:: CrossProcessStoreLock , timeout:: timeout,
99
+ } ;
98
100
pub use memorystore:: MemoryStore ;
99
101
pub use traits:: { CryptoStore , DynCryptoStore , IntoCryptoStore } ;
100
102
103
+ use crate :: types:: {
104
+ events:: room_key_withheld:: RoomKeyWithheldContent , room_history:: RoomKeyBundle ,
105
+ } ;
101
106
pub use crate :: {
102
107
dehydrated_devices:: DehydrationError ,
103
108
gossiping:: { GossipRequest , SecretInfo } ,
@@ -1987,6 +1992,38 @@ impl Store {
1987
1992
Ok ( futures_util:: stream:: iter ( sessions. into_iter ( ) . filter ( predicate) )
1988
1993
. then ( |session| async move { session. export ( ) . await } ) )
1989
1994
}
1995
+
1996
+ /// Assemble a room key bundle for sharing encrypted history, as per
1997
+ /// [MSC4268].
1998
+ ///
1999
+ /// [MSC4268]: https://github.com/matrix-org/matrix-spec-proposals/pull/4268
2000
+ pub async fn build_room_key_bundle (
2001
+ & self ,
2002
+ room_id : & RoomId ,
2003
+ ) -> std:: result:: Result < RoomKeyBundle , CryptoStoreError > {
2004
+ // TODO: make this WAY more efficient. We should only fetch sessions for the
2005
+ // correct room.
2006
+ let mut sessions = self . get_inbound_group_sessions ( ) . await ?;
2007
+ sessions. retain ( |session| session. room_id == room_id) ;
2008
+
2009
+ let mut bundle = RoomKeyBundle :: default ( ) ;
2010
+ for session in sessions {
2011
+ if session. shared_history ( ) {
2012
+ bundle. room_keys . push ( session. export ( ) . await . into ( ) ) ;
2013
+ } else {
2014
+ bundle. withheld . push ( RoomKeyWithheldContent :: new (
2015
+ session. algorithm ( ) . to_owned ( ) ,
2016
+ WithheldCode :: Unauthorised ,
2017
+ session. room_id ( ) . to_owned ( ) ,
2018
+ session. session_id ( ) . to_owned ( ) ,
2019
+ session. sender_key ( ) . to_owned ( ) ,
2020
+ self . device_id ( ) . to_owned ( ) ,
2021
+ ) ) ;
2022
+ }
2023
+ }
2024
+
2025
+ Ok ( bundle)
2026
+ }
1990
2027
}
1991
2028
1992
2029
impl Deref for Store {
@@ -2021,12 +2058,17 @@ mod tests {
2021
2058
use std:: pin:: pin;
2022
2059
2023
2060
use futures_util:: StreamExt ;
2061
+ use insta:: { _macro_support:: Content , assert_json_snapshot, internals:: ContentPath } ;
2024
2062
use matrix_sdk_test:: async_test;
2025
- use ruma:: { room_id, user_id} ;
2063
+ use ruma:: { device_id, room_id, user_id, RoomId } ;
2064
+ use vodozemac:: megolm:: SessionKey ;
2026
2065
2027
2066
use crate :: {
2028
- machine:: test_helpers:: get_machine_pair, store:: DehydratedDeviceKey ,
2067
+ machine:: test_helpers:: get_machine_pair,
2068
+ olm:: { InboundGroupSession , SenderData } ,
2069
+ store:: DehydratedDeviceKey ,
2029
2070
types:: EventEncryptionAlgorithm ,
2071
+ OlmMachine ,
2030
2072
} ;
2031
2073
2032
2074
#[ async_test]
@@ -2190,4 +2232,104 @@ mod tests {
2190
2232
2191
2233
assert ! ( pickle_key. is_err( ) ) ;
2192
2234
}
2235
+
2236
+ #[ async_test]
2237
+ async fn test_build_room_key_bundle ( ) {
2238
+ // Given: Alice has sent a number of room keys to Bob, including some in the
2239
+ // wrong room, and some that are not marked as shared...
2240
+ let alice = OlmMachine :: new ( user_id ! ( "@a:s.co" ) , device_id ! ( "ALICE" ) ) . await ;
2241
+ let bob = OlmMachine :: new ( user_id ! ( "@b:s.co" ) , device_id ! ( "BOB" ) ) . await ;
2242
+
2243
+ let room1_id = room_id ! ( "!room1:localhost" ) ;
2244
+ let room2_id = room_id ! ( "!room2:localhost" ) ;
2245
+
2246
+ /* We use hardcoded megolm session data, to get a stable output snapshot. These were all created with:
2247
+
2248
+ println!("{}", vodozemac::megolm::GroupSession::new(Default::default()).session_key().to_base64());
2249
+ */
2250
+ let session_key1 = "AgAAAAC2XHVzsMBKs4QCRElJ92CJKyGtknCSC8HY7cQ7UYwndMKLQAejXLh5UA0l6s736mgctcUMNvELScUWrObdflrHo+vth/gWreXOaCnaSxmyjjKErQwyIYTkUfqbHy40RJfEesLwnN23on9XAkch/iy8R2+Jz7B8zfG01f2Ow2SxPQFnAndcO1ZSD2GmXgedy6n4B20MWI1jGP2wiexOWbFSya8DO/VxC9m5+/mF+WwYqdpKn9g4Y05Yw4uz7cdjTc3rXm7xK+8E7hI//5QD1nHPvuKYbjjM9u2JSL+Bzp61Cw" ;
2251
+ let session_key2 = "AgAAAAC1BXreFTUQQSBGekTEuYxhdytRKyv4JgDGcG+VOBYdPNGgs807SdibCGJky4lJ3I+7ZDGHoUzZPZP/4ogGu4kxni0PWdtWuN7+5zsuamgoFF/BkaGeUUGv6kgIkx8pyPpM5SASTUEP9bN2loDSpUPYwfiIqz74DgC4WQ4435sTBctYvKz8n+TDJwdLXpyT6zKljuqADAioud+s/iqx9LYn9HpbBfezZcvbg67GtE113pLrvde3IcPI5s6dNHK2onGO2B2eoaobcen18bbEDnlUGPeIivArLya7Da6us14jBQ" ;
2252
+ let session_key3 = "AgAAAAAM9KFsliaUUhGSXgwOzM5UemjkNH4n8NHgvC/y8hhw13zTF+ooGD4uIYEXYX630oNvQm/EvgZo+dkoc0re+vsqsx4sQeNODdSjcBsWOa0oDF+irQn9oYoLUDPI1IBtY1rX+FV99Zm/xnG7uFOX7aTVlko2GSdejy1w9mfobmfxu5aUc04A9zaKJP1pOthZvRAlhpymGYHgsDtWPrrjyc/yypMflE4kIUEEEtu1kT6mrAmcl615XYRAHYK9G2+fZsGvokwzbkl4nulGwcZMpQEoM0nD2o3GWgX81HW3nGfKBg" ;
2253
+ let session_key4 = "AgAAAAA4Kkesxq2h4v9PLD6Sm3Smxspz1PXTqytQPCMQMkkrHNmzV2bHlJ+6/Al9cu8vh1Oj69AK0WUAeJOJuaiskEeg/PI3P03+UYLeC379RzgqwSHdBgdQ41G2vD6zpgmE/8vYToe+qpCZACtPOswZxyqxHH+T/Iq0nv13JmlFGIeA6fEPfr5Y28B49viG74Fs9rxV9EH5PfjbuPM/p+Sz5obShuaBPKQBX1jT913nEXPoIJ06exNZGr0285nw/LgVvNlmWmbqNnbzO2cNZjQWA+xZYz5FSfyCxwqEBbEdUCuRCQ" ;
2254
+
2255
+ let sessions = [
2256
+ create_inbound_group_session_with_visibility (
2257
+ & alice,
2258
+ room1_id,
2259
+ & SessionKey :: from_base64 ( session_key1) . unwrap ( ) ,
2260
+ true ,
2261
+ ) ,
2262
+ create_inbound_group_session_with_visibility (
2263
+ & alice,
2264
+ room1_id,
2265
+ & SessionKey :: from_base64 ( session_key2) . unwrap ( ) ,
2266
+ true ,
2267
+ ) ,
2268
+ create_inbound_group_session_with_visibility (
2269
+ & alice,
2270
+ room1_id,
2271
+ & SessionKey :: from_base64 ( session_key3) . unwrap ( ) ,
2272
+ false ,
2273
+ ) ,
2274
+ create_inbound_group_session_with_visibility (
2275
+ & alice,
2276
+ room2_id,
2277
+ & SessionKey :: from_base64 ( session_key4) . unwrap ( ) ,
2278
+ true ,
2279
+ ) ,
2280
+ ] ;
2281
+ bob. store ( ) . save_inbound_group_sessions ( & sessions) . await . unwrap ( ) ;
2282
+
2283
+ // When I build the bundle
2284
+ let mut bundle = bob. store ( ) . build_room_key_bundle ( room1_id) . await . unwrap ( ) ;
2285
+
2286
+ // Then the bundle matches the snapshot.
2287
+
2288
+ // We sort the sessions in the bundle, so that the snapshot is stable.
2289
+ bundle. room_keys . sort_by_key ( |session| session. session_id . clone ( ) ) ;
2290
+
2291
+ // We also substitute alice's keys in the snapshot with placeholders
2292
+ let alice_curve_key = alice. identity_keys ( ) . curve25519 . to_base64 ( ) ;
2293
+ let map_alice_curve_key = move |value : Content , _path : ContentPath < ' _ > | {
2294
+ assert_eq ! ( value. as_str( ) . unwrap( ) , alice_curve_key) ;
2295
+ "[alice curve key]"
2296
+ } ;
2297
+ let alice_ed25519_key = alice. identity_keys ( ) . ed25519 . to_base64 ( ) ;
2298
+ let map_alice_ed25519_key = move |value : Content , _path : ContentPath < ' _ > | {
2299
+ assert_eq ! ( value. as_str( ) . unwrap( ) , alice_ed25519_key) ;
2300
+ "[alice ed25519 key]"
2301
+ } ;
2302
+
2303
+ insta:: with_settings!( { sort_maps => true } , {
2304
+ assert_json_snapshot!( bundle, {
2305
+ ".room_keys[].sender_key" => insta:: dynamic_redaction( map_alice_curve_key. clone( ) ) ,
2306
+ ".withheld[].sender_key" => insta:: dynamic_redaction( map_alice_curve_key) ,
2307
+ ".room_keys[].sender_claimed_keys.ed25519" => insta:: dynamic_redaction( map_alice_ed25519_key) ,
2308
+ } ) ;
2309
+ } ) ;
2310
+ }
2311
+
2312
+ /// Create an inbound Megolm session for the given room.
2313
+ ///
2314
+ /// `olm_machine` is used to set the `sender_key` and `signing_key`
2315
+ /// fields of the resultant session.
2316
+ fn create_inbound_group_session_with_visibility (
2317
+ olm_machine : & OlmMachine ,
2318
+ room_id : & RoomId ,
2319
+ session_key : & SessionKey ,
2320
+ shared_history : bool ,
2321
+ ) -> InboundGroupSession {
2322
+ let identity_keys = & olm_machine. store ( ) . static_account ( ) . identity_keys ;
2323
+ InboundGroupSession :: new (
2324
+ identity_keys. curve25519 ,
2325
+ identity_keys. ed25519 ,
2326
+ room_id,
2327
+ session_key,
2328
+ SenderData :: unknown ( ) ,
2329
+ EventEncryptionAlgorithm :: MegolmV1AesSha2 ,
2330
+ None ,
2331
+ shared_history,
2332
+ )
2333
+ . unwrap ( )
2334
+ }
2193
2335
}
0 commit comments