@@ -23,11 +23,13 @@ use matrix_sdk_crypto::{
23
23
} ;
24
24
use serde:: { ser:: SerializeSeq , Serialize , Serializer } ;
25
25
use serde_json:: json;
26
- use tracing:: warn;
26
+ use tracing:: { info , warn} ;
27
27
use wasm_bindgen:: { convert:: TryFromJsValue , prelude:: * } ;
28
28
use wasm_bindgen_futures:: { spawn_local, JsFuture } ;
29
+ use zeroize:: Zeroizing ;
29
30
30
31
use crate :: {
32
+ attachment,
31
33
backup:: { BackupDecryptionKey , BackupKeys , RoomKeyCounts } ,
32
34
dehydrated_devices:: DehydratedDevices ,
33
35
device, encryption,
@@ -41,7 +43,7 @@ use crate::{
41
43
sync_events,
42
44
types:: {
43
45
self , processed_to_device_event_to_js_value, RoomKeyImportResult , RoomSettings ,
44
- SignatureVerification ,
46
+ SignatureVerification , StoredRoomKeyBundleData ,
45
47
} ,
46
48
verification, vodozemac,
47
49
} ;
@@ -1584,6 +1586,146 @@ impl OlmMachine {
1584
1586
self . inner . dehydrated_devices ( ) . into ( )
1585
1587
}
1586
1588
1589
+ /// Assemble, and encrypt, a room key bundle for sharing encrypted history,
1590
+ /// as per {@link MSC4268 | https://github.com/matrix-org/matrix-spec-proposals/pull/4268}.
1591
+ ///
1592
+ /// Returns `undefined` if there are no keys to share in the given room,
1593
+ /// otherwise an {@link EncryptedAttachment}.
1594
+ ///
1595
+ /// The data should be uploaded to the media server, and the details then
1596
+ /// passed to {@link shareRoomKeyBundleData}.
1597
+ ///
1598
+ /// @experimental
1599
+ #[ wasm_bindgen(
1600
+ js_name = "buildRoomKeyBundle" ,
1601
+ unchecked_return_type = "Promise<EncryptedAttachment | undefined>"
1602
+ ) ]
1603
+ pub fn build_room_key_bundle ( & self , room_id : & identifiers:: RoomId ) -> Promise {
1604
+ let me = self . inner . clone ( ) ;
1605
+ let room_id = room_id. inner . clone ( ) ;
1606
+
1607
+ future_to_promise ( async move {
1608
+ let bundle = me. store ( ) . build_room_key_bundle ( & room_id) . await ?;
1609
+
1610
+ if bundle. is_empty ( ) {
1611
+ info ! ( "No keys to share" ) ;
1612
+ return Ok ( None ) ;
1613
+ }
1614
+
1615
+ // Remember to zeroize the json as soon as we're done with it
1616
+ let json = Zeroizing :: new ( serde_json:: to_vec ( & bundle) ?) ;
1617
+
1618
+ Ok ( Some ( attachment:: Attachment :: encrypt ( json. as_slice ( ) ) ?) )
1619
+ } )
1620
+ }
1621
+
1622
+ /// Collect the devices belonging to the given user, and send the details
1623
+ /// of a room key bundle to those devices.
1624
+ ///
1625
+ /// Returns a list of to-device requests which must be sent.
1626
+ ///
1627
+ /// @experimental
1628
+ #[ wasm_bindgen(
1629
+ js_name = "shareRoomKeyBundleData" ,
1630
+ unchecked_return_type = "Promise<ToDeviceRequest[]>"
1631
+ ) ]
1632
+ pub fn share_room_key_bundle_data (
1633
+ & self ,
1634
+ user : & identifiers:: UserId ,
1635
+ sharing_strategy : encryption:: CollectStrategy ,
1636
+ content : JsValue ,
1637
+ ) -> Result < Promise , JsError > {
1638
+ let me = self . inner . clone ( ) ;
1639
+ let user_id = user. inner . clone ( ) ;
1640
+ let bundle_data = serde_wasm_bindgen:: from_value ( content) . map_err ( |e| {
1641
+ JsError :: new ( & format ! ( "Unable to validate room key bundle content: {e}" ) )
1642
+ } ) ?;
1643
+
1644
+ Ok ( future_to_promise ( async move {
1645
+ let to_device_requests = me
1646
+ . share_room_key_bundle_data ( & user_id, & sharing_strategy. into ( ) , bundle_data)
1647
+ . await ?;
1648
+
1649
+ // convert each request to our own ToDeviceRequest struct, and then wrap it in a
1650
+ // JsValue.
1651
+ //
1652
+ // Then collect the results into a javascript Array, throwing any errors into
1653
+ // the promise.
1654
+ let result = to_device_requests
1655
+ . into_iter ( )
1656
+ . map ( |td| ToDeviceRequest :: try_from ( & td) . map ( JsValue :: from) )
1657
+ . collect :: < Result < Array , _ > > ( ) ?;
1658
+
1659
+ Ok ( result)
1660
+ } ) )
1661
+ }
1662
+
1663
+ /// See if we have received an {@link MSC4268 | https://github.com/matrix-org/matrix-spec-proposals/pull/4268}
1664
+ /// room key bundle for the given room from the given user.
1665
+ ///
1666
+ /// Returns either `undefined` if no suitable bundle has been received,
1667
+ /// or an {@link StoredRoomKeyBundleData}, in which case, the bundle
1668
+ /// should be downloaded, and then passed to {@link
1669
+ /// receiveRoomKeyBundle}.
1670
+ ///
1671
+ /// @experimental
1672
+ #[ wasm_bindgen(
1673
+ js_name = "getReceivedRoomKeyBundleData" ,
1674
+ unchecked_return_type = "Promise<StoredRoomKeyBundleData | undefined>"
1675
+ ) ]
1676
+ pub fn get_received_room_key_bundle_data (
1677
+ & self ,
1678
+ room_id : & identifiers:: RoomId ,
1679
+ inviter : & identifiers:: UserId ,
1680
+ ) -> Promise {
1681
+ let me = self . inner . clone ( ) ;
1682
+ let room_id = room_id. inner . clone ( ) ;
1683
+ let inviter = inviter. inner . clone ( ) ;
1684
+
1685
+ future_to_promise ( async move {
1686
+ let result = me
1687
+ . store ( )
1688
+ . get_received_room_key_bundle_data ( & room_id, & inviter)
1689
+ . await ?
1690
+ . map ( types:: StoredRoomKeyBundleData :: from) ;
1691
+ Ok ( result)
1692
+ } )
1693
+ }
1694
+
1695
+ /// Import the message keys from a downloaded room key bundle.
1696
+ ///
1697
+ /// After {@link getReceivedRoomKeyBundleData} returns a truthy result, the
1698
+ /// media file should be downloaded and then passed into this method to
1699
+ /// actually do the import.
1700
+ ///
1701
+ /// @experimental
1702
+ #[ wasm_bindgen( js_name = "receiveRoomKeyBundle" , unchecked_return_type = "Promise<undefined>" ) ]
1703
+ pub fn receive_room_key_bundle (
1704
+ & self ,
1705
+ bundle_data : & StoredRoomKeyBundleData ,
1706
+ mut bundle : attachment:: EncryptedAttachment ,
1707
+ ) -> Result < Promise , JsError > {
1708
+ let me = self . inner . clone ( ) ;
1709
+ let bundle_data = bundle_data. clone ( ) ;
1710
+
1711
+ let decrypted_bundle = attachment:: Attachment :: decrypt ( & mut bundle) ?;
1712
+ let deserialized_bundle = serde_json:: from_slice ( & decrypted_bundle) ?;
1713
+
1714
+ Ok ( future_to_promise ( async move {
1715
+ me. store ( )
1716
+ . receive_room_key_bundle (
1717
+ & bundle_data. room_id . inner ,
1718
+ & bundle_data. sender_user . inner ,
1719
+ & bundle_data. sender_data ,
1720
+ deserialized_bundle,
1721
+ /* TODO: Use the progress listener and expose an argument for it. */
1722
+ |_, _| { } ,
1723
+ )
1724
+ . await ?;
1725
+ Ok ( JsValue :: UNDEFINED )
1726
+ } ) )
1727
+ }
1728
+
1587
1729
/// Shut down the `OlmMachine`.
1588
1730
///
1589
1731
/// The `OlmMachine` cannot be used after this method has been called.
0 commit comments