@@ -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,36 @@ 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
+ pub async fn build_room_key_bundle (
1999
+ & self ,
2000
+ room_id : & RoomId ,
2001
+ ) -> std:: result:: Result < RoomKeyBundle , CryptoStoreError > {
2002
+ // TODO: make this WAY more efficient. We should only fetch sessions for the
2003
+ // correct room.
2004
+ let mut sessions = self . get_inbound_group_sessions ( ) . await ?;
2005
+ sessions. retain ( |session| session. room_id == room_id) ;
2006
+
2007
+ let mut bundle = RoomKeyBundle :: default ( ) ;
2008
+ for session in sessions {
2009
+ if session. shared_history ( ) {
2010
+ bundle. room_keys . push ( session. export ( ) . await . into ( ) ) ;
2011
+ } else {
2012
+ bundle. withheld . push ( RoomKeyWithheldContent :: new (
2013
+ session. algorithm ( ) . to_owned ( ) ,
2014
+ WithheldCode :: Unauthorised ,
2015
+ session. room_id ( ) . to_owned ( ) ,
2016
+ session. session_id ( ) . to_owned ( ) ,
2017
+ session. sender_key ( ) . to_owned ( ) ,
2018
+ self . device_id ( ) . to_owned ( ) ,
2019
+ ) ) ;
2020
+ }
2021
+ }
2022
+
2023
+ Ok ( bundle)
2024
+ }
1990
2025
}
1991
2026
1992
2027
impl Deref for Store {
@@ -2021,12 +2056,17 @@ mod tests {
2021
2056
use std:: pin:: pin;
2022
2057
2023
2058
use futures_util:: StreamExt ;
2059
+ use insta:: assert_json_snapshot;
2024
2060
use matrix_sdk_test:: async_test;
2025
- use ruma:: { room_id, user_id} ;
2061
+ use ruma:: { device_id, room_id, user_id, DeviceId , RoomId , UserId } ;
2062
+ use vodozemac:: megolm:: SessionKey ;
2026
2063
2027
2064
use crate :: {
2028
- machine:: test_helpers:: get_machine_pair, store:: DehydratedDeviceKey ,
2065
+ machine:: test_helpers:: get_machine_pair,
2066
+ olm:: { InboundGroupSession , SenderData } ,
2067
+ store:: { DehydratedDeviceKey , MemoryStore } ,
2029
2068
types:: EventEncryptionAlgorithm ,
2069
+ OlmMachine ,
2030
2070
} ;
2031
2071
2032
2072
#[ async_test]
@@ -2190,4 +2230,138 @@ mod tests {
2190
2230
2191
2231
assert ! ( pickle_key. is_err( ) ) ;
2192
2232
}
2233
+
2234
+ #[ async_test]
2235
+ async fn test_build_room_key_bundle ( ) {
2236
+ // Given: Alice has sent a number of room keys to Bob, including some in the
2237
+ // wrong room, and some that are not marked as shared...
2238
+ let alice = olm_machine_from_account_pickle (
2239
+ user_id ! ( "@a:s.co" ) ,
2240
+ device_id ! ( "ALICE" ) ,
2241
+ olm_pickle_data1 ( ) ,
2242
+ )
2243
+ . await ;
2244
+
2245
+ let bob = olm_machine_from_account_pickle (
2246
+ user_id ! ( "@b:s.co" ) ,
2247
+ device_id ! ( "BOB" ) ,
2248
+ olm_pickle_data2 ( ) ,
2249
+ )
2250
+ . await ;
2251
+
2252
+ let room1_id = room_id ! ( "!room1:localhost" ) ;
2253
+ let room2_id = room_id ! ( "!room2:localhost" ) ;
2254
+
2255
+ /* base64-encoded Megolm session keys, created with:
2256
+ println!(
2257
+ "{}",
2258
+ vodozemac::megolm::GroupSession::new(Default::default()).session_key().to_base64()
2259
+ );
2260
+ */
2261
+
2262
+ let session_key1 = "AgAAAAC2XHVzsMBKs4QCRElJ92CJKyGtknCSC8HY7cQ7UYwndMKLQAejXLh5UA0l6s736mgctcUMNvELScUWrObdflrHo+vth/gWreXOaCnaSxmyjjKErQwyIYTkUfqbHy40RJfEesLwnN23on9XAkch/iy8R2+Jz7B8zfG01f2Ow2SxPQFnAndcO1ZSD2GmXgedy6n4B20MWI1jGP2wiexOWbFSya8DO/VxC9m5+/mF+WwYqdpKn9g4Y05Yw4uz7cdjTc3rXm7xK+8E7hI//5QD1nHPvuKYbjjM9u2JSL+Bzp61Cw" ;
2263
+ let session_key2 = "AgAAAAC1BXreFTUQQSBGekTEuYxhdytRKyv4JgDGcG+VOBYdPNGgs807SdibCGJky4lJ3I+7ZDGHoUzZPZP/4ogGu4kxni0PWdtWuN7+5zsuamgoFF/BkaGeUUGv6kgIkx8pyPpM5SASTUEP9bN2loDSpUPYwfiIqz74DgC4WQ4435sTBctYvKz8n+TDJwdLXpyT6zKljuqADAioud+s/iqx9LYn9HpbBfezZcvbg67GtE113pLrvde3IcPI5s6dNHK2onGO2B2eoaobcen18bbEDnlUGPeIivArLya7Da6us14jBQ" ;
2264
+ let session_key3 = "AgAAAAAM9KFsliaUUhGSXgwOzM5UemjkNH4n8NHgvC/y8hhw13zTF+ooGD4uIYEXYX630oNvQm/EvgZo+dkoc0re+vsqsx4sQeNODdSjcBsWOa0oDF+irQn9oYoLUDPI1IBtY1rX+FV99Zm/xnG7uFOX7aTVlko2GSdejy1w9mfobmfxu5aUc04A9zaKJP1pOthZvRAlhpymGYHgsDtWPrrjyc/yypMflE4kIUEEEtu1kT6mrAmcl615XYRAHYK9G2+fZsGvokwzbkl4nulGwcZMpQEoM0nD2o3GWgX81HW3nGfKBg" ;
2265
+ let session_key4 = "AgAAAAA4Kkesxq2h4v9PLD6Sm3Smxspz1PXTqytQPCMQMkkrHNmzV2bHlJ+6/Al9cu8vh1Oj69AK0WUAeJOJuaiskEeg/PI3P03+UYLeC379RzgqwSHdBgdQ41G2vD6zpgmE/8vYToe+qpCZACtPOswZxyqxHH+T/Iq0nv13JmlFGIeA6fEPfr5Y28B49viG74Fs9rxV9EH5PfjbuPM/p+Sz5obShuaBPKQBX1jT913nEXPoIJ06exNZGr0285nw/LgVvNlmWmbqNnbzO2cNZjQWA+xZYz5FSfyCxwqEBbEdUCuRCQ" ;
2266
+
2267
+ let sessions = [
2268
+ create_inbound_group_session_with_visibility (
2269
+ & alice,
2270
+ room1_id,
2271
+ & SessionKey :: from_base64 ( session_key1) . unwrap ( ) ,
2272
+ true ,
2273
+ ) ,
2274
+ create_inbound_group_session_with_visibility (
2275
+ & alice,
2276
+ room1_id,
2277
+ & SessionKey :: from_base64 ( session_key2) . unwrap ( ) ,
2278
+ true ,
2279
+ ) ,
2280
+ create_inbound_group_session_with_visibility (
2281
+ & alice,
2282
+ room1_id,
2283
+ & SessionKey :: from_base64 ( session_key3) . unwrap ( ) ,
2284
+ false ,
2285
+ ) ,
2286
+ create_inbound_group_session_with_visibility (
2287
+ & alice,
2288
+ room2_id,
2289
+ & SessionKey :: from_base64 ( session_key4) . unwrap ( ) ,
2290
+ true ,
2291
+ ) ,
2292
+ ] ;
2293
+ bob. store ( ) . save_inbound_group_sessions ( & sessions) . await . unwrap ( ) ;
2294
+
2295
+ // When I build the bundle
2296
+ let mut bundle = bob. store ( ) . build_room_key_bundle ( room1_id) . await . unwrap ( ) ;
2297
+
2298
+ // Then the bundle matches the snapshot.
2299
+ // We sort the sessions in the bundle, so that the snapshot is stable.
2300
+ bundle. room_keys . sort_by_key ( |session| session. session_id . clone ( ) ) ;
2301
+ insta:: with_settings!( { sort_maps => true } , {
2302
+ assert_json_snapshot!( bundle) ;
2303
+ } ) ;
2304
+ }
2305
+
2306
+ /// Create a test [`OlmMachine`], backed by an in-memory store, based on the
2307
+ /// given pickle data.
2308
+ async fn olm_machine_from_account_pickle (
2309
+ user_id : & UserId ,
2310
+ device_id : & DeviceId ,
2311
+ pickle : vodozemac:: olm:: AccountPickle ,
2312
+ ) -> OlmMachine {
2313
+ OlmMachine :: with_store ( user_id, device_id, MemoryStore :: new ( ) , Some ( pickle. into ( ) ) )
2314
+ . await
2315
+ . unwrap ( )
2316
+ }
2317
+
2318
+ /// A hardcoded set of device keys, suitable for creating a test olm machine
2319
+ /// with.
2320
+ fn olm_pickle_data1 ( ) -> vodozemac:: olm:: AccountPickle {
2321
+ let alice_account_pickle = serde_json:: json!( {
2322
+ "signing_key" : { "Normal" : b"alicesigningkey12345678901234567" } ,
2323
+ "diffie_hellman_key" : b"alicediffiehelmankey123456789012" ,
2324
+ "one_time_keys" : { "next_key_id" : 0 , "public_keys" : { } , "private_keys" : { } } ,
2325
+ "fallback_keys" : { "key_id" : 0 , "fallback_key" : null, "previous_fallback_key" : null}
2326
+ } ) ;
2327
+
2328
+ serde_json:: from_value ( alice_account_pickle) . unwrap ( )
2329
+ }
2330
+
2331
+ /// Another set hardcoded set of device keys, suitable for creating a test
2332
+ /// olm machine with.
2333
+ fn olm_pickle_data2 ( ) -> vodozemac:: olm:: AccountPickle {
2334
+ let alice_account_pickle = serde_json:: json!( {
2335
+ "signing_key" : { "Normal" : b"alicesigningkey12345678901234567" } ,
2336
+ "diffie_hellman_key" : b"alicediffiehelmankey123456789012" ,
2337
+ "one_time_keys" : { "next_key_id" : 0 , "public_keys" : { } , "private_keys" : { } } ,
2338
+ "fallback_keys" : { "key_id" : 0 , "fallback_key" : null, "previous_fallback_key" : null}
2339
+ } ) ;
2340
+
2341
+ serde_json:: from_value ( alice_account_pickle) . unwrap ( )
2342
+ }
2343
+
2344
+ /// Create an inbound Megolm session for the given room.
2345
+ ///
2346
+ /// `olm_machine` is used to set the `sender_key` and `signing_key`
2347
+ /// fields of the resultant session.
2348
+ fn create_inbound_group_session_with_visibility (
2349
+ olm_machine : & OlmMachine ,
2350
+ room_id : & RoomId ,
2351
+ session_key : & SessionKey ,
2352
+ shared_history : bool ,
2353
+ ) -> InboundGroupSession {
2354
+ let identity_keys = & olm_machine. store ( ) . static_account ( ) . identity_keys ;
2355
+ InboundGroupSession :: new (
2356
+ identity_keys. curve25519 ,
2357
+ identity_keys. ed25519 ,
2358
+ room_id,
2359
+ session_key,
2360
+ SenderData :: unknown ( ) ,
2361
+ EventEncryptionAlgorithm :: MegolmV1AesSha2 ,
2362
+ None ,
2363
+ shared_history,
2364
+ )
2365
+ . unwrap ( )
2366
+ }
2193
2367
}
0 commit comments