From ffd2843b0aa303c2beacabaf72e6ee8bdd265f82 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Fri, 11 Sep 2020 16:34:39 +0200 Subject: [PATCH] matrix-sdk: Expose the import/export keys methods. --- matrix_sdk/Cargo.toml | 6 + matrix_sdk/src/client.rs | 130 +++++++++++++++++- matrix_sdk/src/device.rs | 4 +- matrix_sdk/src/error.rs | 2 +- matrix_sdk/src/lib.rs | 6 +- matrix_sdk/src/sas.rs | 2 +- matrix_sdk_base/src/client.rs | 6 + matrix_sdk_base/src/lib.rs | 5 +- matrix_sdk_common_macros/Cargo.toml | 2 +- matrix_sdk_crypto/Cargo.toml | 2 +- .../src/olm/group_sessions/inbound.rs | 4 + 11 files changed, 154 insertions(+), 15 deletions(-) diff --git a/matrix_sdk/Cargo.toml b/matrix_sdk/Cargo.toml index c8edc1c7..44e3cfbf 100644 --- a/matrix_sdk/Cargo.toml +++ b/matrix_sdk/Cargo.toml @@ -34,6 +34,7 @@ serde_json = "1.0.57" thiserror = "1.0.20" tracing = "0.1.19" url = "2.1.1" +zeroize = "1.1.0" matrix-sdk-common-macros = { version = "0.1.0", path = "../matrix_sdk_common_macros" } matrix-sdk-common = { version = "0.1.0", path = "../matrix_sdk_common" } @@ -55,6 +56,11 @@ features = ["std", "std-future"] [target.'cfg(not(target_arch = "wasm32"))'.dependencies] futures-timer = "3.0.2" +[target.'cfg(not(target_arch = "wasm32"))'.dependencies.tokio] +version = "0.2.22" +default-features = false +features = ["fs", "blocking"] + [target.'cfg(target_arch = "wasm32")'.dependencies.futures-timer] version = "3.0.2" features = ["wasm-bindgen"] diff --git a/matrix_sdk/src/client.rs b/matrix_sdk/src/client.rs index 73e1b8d5..4c0daa27 100644 --- a/matrix_sdk/src/client.rs +++ b/matrix_sdk/src/client.rs @@ -22,12 +22,16 @@ use std::{ result::Result as StdResult, sync::Arc, }; +#[cfg(feature = "encryption")] +use std::{io::Write, path::PathBuf}; #[cfg(feature = "encryption")] use dashmap::DashMap; use futures_timer::Delay as sleep; use reqwest::header::{HeaderValue, InvalidHeaderValue}; use url::Url; +#[cfg(feature = "encryption")] +use zeroize::Zeroizing; #[cfg(feature = "encryption")] use tracing::{debug, warn}; @@ -36,7 +40,10 @@ use tracing::{error, info, instrument}; use matrix_sdk_base::{BaseClient, BaseClientConfig, Room, Session, StateStore}; #[cfg(feature = "encryption")] -use matrix_sdk_base::{CryptoStoreError, OutgoingRequests, ToDeviceRequest}; +use matrix_sdk_base::crypto::{ + decrypt_key_export, encrypt_key_export, olm::InboundGroupSession, store::CryptoStoreError, + OutgoingRequests, ToDeviceRequest, +}; use matrix_sdk_common::{ api::r0::{ @@ -1479,7 +1486,6 @@ impl Client { /// /// println!("{:?}", device.is_trusted()); /// - /// /// let verification = device.start_verification().await.unwrap(); /// # }); /// ``` @@ -1534,6 +1540,126 @@ impl Client { http_client: self.http_client.clone(), }) } + + /// Export E2EE keys that match the given predicate encrypting them with the + /// given passphrase. + /// + /// # Arguments + /// + /// * `path` - The file path where the exported key file will be saved. + /// + /// * `passphrase` - The passphrase that will be used to encrypt the exported + /// room keys. + /// + /// * `predicate` - A closure that will be called for every known + /// `InboundGroupSession`, which represents a room key. If the closure + /// returns `true` the `InboundGroupSessoin` will be included in the export, + /// if the closure returns `false` it will not be included. + /// + /// # Examples + /// + /// ```no_run + /// # use std::{path::PathBuf, time::Duration}; + /// # use matrix_sdk::{ + /// # Client, SyncSettings, + /// # api::r0::typing::create_typing_event::Typing, + /// # identifiers::room_id, + /// # }; + /// # use futures::executor::block_on; + /// # use url::Url; + /// # block_on(async { + /// # let homeserver = Url::parse("http://localhost:8080").unwrap(); + /// # let mut client = Client::new(homeserver).unwrap(); + /// let path = PathBuf::from("/home/example/e2e-keys.txt"); + /// // Export all room keys. + /// client + /// .export_keys(path, "secret-passphrase", |_| true) + /// .await + /// .expect("Can't export keys."); + /// + /// // Export only the room keys for a certain room. + /// let path = PathBuf::from("/home/example/e2e-room-keys.txt"); + /// let room_id = room_id!("!test:localhost"); + /// + /// client + /// .export_keys(path, "secret-passphrase", |s| s.room_id() == &room_id) + /// .await + /// .expect("Can't export keys."); + /// # }); + /// ``` + #[cfg(feature = "encryption")] + #[cfg(not(target_arch = "wasm32"))] + #[cfg_attr(feature = "docs", doc(cfg(encryption)))] + pub async fn export_keys( + &self, + path: PathBuf, + passphrase: &str, + predicate: impl FnMut(&InboundGroupSession) -> bool, + ) -> Result<()> { + let olm = self + .base_client + .olm_machine() + .await + .ok_or(Error::AuthenticationRequired)?; + + let keys = olm.export_keys(predicate).await?; + let passphrase = Zeroizing::new(passphrase.to_owned()); + + let encrypt = move || -> Result<()> { + let export: String = encrypt_key_export(&keys, &passphrase, 500_000)?; + let mut file = std::fs::File::create(path).unwrap(); + file.write_all(&export.into_bytes()).unwrap(); + Ok(()) + }; + + let task = tokio::task::spawn_blocking(encrypt); + task.await.expect("Task join error") + } + + /// Import E2EE keys from the given file path. + /// + /// ```no_run + /// # use std::{path::PathBuf, time::Duration}; + /// # use matrix_sdk::{ + /// # Client, SyncSettings, + /// # api::r0::typing::create_typing_event::Typing, + /// # identifiers::room_id, + /// # }; + /// # use futures::executor::block_on; + /// # use url::Url; + /// # block_on(async { + /// # let homeserver = Url::parse("http://localhost:8080").unwrap(); + /// # let mut client = Client::new(homeserver).unwrap(); + /// let path = PathBuf::from("/home/example/e2e-keys.txt"); + /// client + /// .import_keys(path, "secret-passphrase") + /// .await + /// .expect("Can't import keys"); + /// # }); + /// ``` + #[cfg(feature = "encryption")] + #[cfg(not(target_arch = "wasm32"))] + #[cfg_attr(feature = "docs", doc(cfg(encryption)))] + pub async fn import_keys(&self, path: PathBuf, passphrase: &str) -> Result<()> { + let olm = self + .base_client + .olm_machine() + .await + .ok_or(Error::AuthenticationRequired)?; + let passphrase = Zeroizing::new(passphrase.to_owned()); + + let decrypt = move || { + let file = std::fs::File::open(path)?; + decrypt_key_export(file, &passphrase) + }; + + let task = tokio::task::spawn_blocking(decrypt); + let import = task.await.expect("Task join error").unwrap(); + + olm.import_keys(import).await.unwrap(); + + Ok(()) + } } #[cfg(test)] diff --git a/matrix_sdk/src/device.rs b/matrix_sdk/src/device.rs index 7173e266..03ca9c44 100644 --- a/matrix_sdk/src/device.rs +++ b/matrix_sdk/src/device.rs @@ -14,8 +14,8 @@ use std::{ops::Deref, result::Result as StdResult}; -use matrix_sdk_base::{ - CryptoStoreError, Device as BaseDevice, LocalTrust, ReadOnlyDevice, +use matrix_sdk_base::crypto::{ + store::CryptoStoreError, Device as BaseDevice, LocalTrust, ReadOnlyDevice, UserDevices as BaseUserDevices, }; use matrix_sdk_common::{ diff --git a/matrix_sdk/src/error.rs b/matrix_sdk/src/error.rs index 0415f902..943c6f05 100644 --- a/matrix_sdk/src/error.rs +++ b/matrix_sdk/src/error.rs @@ -24,7 +24,7 @@ use serde_json::Error as JsonError; use thiserror::Error; #[cfg(feature = "encryption")] -use matrix_sdk_base::CryptoStoreError; +use matrix_sdk_base::crypto::store::CryptoStoreError; /// Result type of the rust-sdk. pub type Result = std::result::Result; diff --git a/matrix_sdk/src/lib.rs b/matrix_sdk/src/lib.rs index 6817a10b..d4dce80b 100644 --- a/matrix_sdk/src/lib.rs +++ b/matrix_sdk/src/lib.rs @@ -44,11 +44,11 @@ compile_error!("one of 'native-tls' or 'rustls-tls' features must be enabled"); #[cfg(all(feature = "native-tls", feature = "rustls-tls",))] compile_error!("only one of 'native-tls' or 'rustls-tls' features can be enabled"); -#[cfg(not(target_arch = "wasm32"))] -pub use matrix_sdk_base::JsonStore; #[cfg(feature = "encryption")] #[cfg_attr(feature = "docs", doc(cfg(encryption)))] -pub use matrix_sdk_base::LocalTrust; +pub use matrix_sdk_base::crypto::LocalTrust; +#[cfg(not(target_arch = "wasm32"))] +pub use matrix_sdk_base::JsonStore; pub use matrix_sdk_base::{ CustomEvent, Error as BaseError, EventEmitter, Room, RoomState, Session, StateStore, SyncRoom, }; diff --git a/matrix_sdk/src/sas.rs b/matrix_sdk/src/sas.rs index 2a8e8708..975548ce 100644 --- a/matrix_sdk/src/sas.rs +++ b/matrix_sdk/src/sas.rs @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use matrix_sdk_base::{ReadOnlyDevice, Sas as BaseSas}; +use matrix_sdk_base::crypto::{ReadOnlyDevice, Sas as BaseSas}; use matrix_sdk_common::api::r0::to_device::send_event_to_device::Request as ToDeviceRequest; use crate::{error::Result, http_client::HttpClient}; diff --git a/matrix_sdk_base/src/client.rs b/matrix_sdk_base/src/client.rs index 9ecf24ca..57c2c1ab 100644 --- a/matrix_sdk_base/src/client.rs +++ b/matrix_sdk_base/src/client.rs @@ -1860,6 +1860,12 @@ impl BaseClient { panic!("The client hasn't been logged in") } } + + /// Get the olm machine. + pub async fn olm_machine(&self) -> Option { + let olm = self.olm.lock().await; + olm.as_ref().cloned() + } } #[cfg(test)] diff --git a/matrix_sdk_base/src/lib.rs b/matrix_sdk_base/src/lib.rs index 9ba33445..5b52ec62 100644 --- a/matrix_sdk_base/src/lib.rs +++ b/matrix_sdk_base/src/lib.rs @@ -56,10 +56,7 @@ pub use state::{AllRooms, ClientState}; #[cfg(feature = "encryption")] #[cfg_attr(feature = "docs", doc(cfg(encryption)))] -pub use matrix_sdk_crypto::{ - store::CryptoStoreError, Device, IncomingResponse, LocalTrust, OutgoingRequest, - OutgoingRequests, ReadOnlyDevice, Sas, ToDeviceRequest, UserDevices, -}; +pub use matrix_sdk_crypto as crypto; #[cfg(feature = "messages")] #[cfg_attr(feature = "docs", doc(cfg(messages)))] diff --git a/matrix_sdk_common_macros/Cargo.toml b/matrix_sdk_common_macros/Cargo.toml index c17a3c44..b48aa9b1 100644 --- a/matrix_sdk_common_macros/Cargo.toml +++ b/matrix_sdk_common_macros/Cargo.toml @@ -14,5 +14,5 @@ version = "0.1.0" proc-macro = true [dependencies] -syn = "1.0.39" +syn = "1.0.40" quote = "1.0.7" diff --git a/matrix_sdk_crypto/Cargo.toml b/matrix_sdk_crypto/Cargo.toml index 1ed7498c..aa3bbb3a 100644 --- a/matrix_sdk_crypto/Cargo.toml +++ b/matrix_sdk_crypto/Cargo.toml @@ -26,7 +26,7 @@ matrix-sdk-common-macros = { version = "0.1.0", path = "../matrix_sdk_common_mac matrix-sdk-common = { version = "0.1.0", path = "../matrix_sdk_common" } olm-rs = { version = "0.6.0", features = ["serde"] } -getrandom = "0.1.14" +getrandom = "0.2.0" serde = { version = "1.0.115", features = ["derive", "rc"] } serde_json = "1.0.57" cjson = "0.1.1" diff --git a/matrix_sdk_crypto/src/olm/group_sessions/inbound.rs b/matrix_sdk_crypto/src/olm/group_sessions/inbound.rs index a35c6190..733b29fe 100644 --- a/matrix_sdk_crypto/src/olm/group_sessions/inbound.rs +++ b/matrix_sdk_crypto/src/olm/group_sessions/inbound.rs @@ -40,6 +40,10 @@ pub use olm_rs::{ use super::{ExportedGroupSessionKey, ExportedRoomKey, GroupSessionKey}; use crate::error::{EventError, MegolmResult}; +// TODO add creation times to the inbound grop sessions so we can export +// sessions that were created between some time period, this should only be set +// for non-imported sessoins. + /// Inbound group session. /// /// Inbound group sessions are used to exchange room messages between a group of