diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 00000000..b2c9bde3 --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,2 @@ +[doc.extern-map.registries] +crates-io = "https://docs.rs/" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 059d5a26..517a939b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -94,19 +94,10 @@ jobs: strategy: matrix: name: - - linux / appservice / stable / actix - - macOS / appservice / stable / actix - linux / appservice / stable / warp - macOS / appservice / stable / warp include: - - name: linux / appservice / stable / actix - cargo_args: --no-default-features --features actix - - - name: macOS / appservice / stable / actix - os: macOS-latest - cargo_args: --no-default-features --features actix - - name: linux / appservice / stable / warp cargo_args: --features warp diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index feabc582..6a8d9a7a 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -27,7 +27,7 @@ jobs: RUSTDOCFLAGS: "--enable-index-page -Zunstable-options" with: command: doc - args: --no-deps --workspace --features docs + args: --no-deps --workspace --features docs -Zrustdoc-map - name: Deploy docs if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }} diff --git a/matrix_qrcode/Cargo.toml b/matrix_qrcode/Cargo.toml index 28edd7d6..8dc9cbd5 100644 --- a/matrix_qrcode/Cargo.toml +++ b/matrix_qrcode/Cargo.toml @@ -1,8 +1,14 @@ [package] name = "matrix-qrcode" +description = "Library to encode and decode QR codes for interactive verifications in Matrix land" version = "0.1.0" authors = ["Damir Jelić "] edition = "2018" +homepage = "https://github.com/matrix-org/matrix-rust-sdk" +keywords = ["matrix", "chat", "messaging", "ruma", "nio"] +license = "Apache-2.0" +readme = "README.md" +repository = "https://github.com/matrix-org/matrix-rust-sdk" [package.metadata.docs.rs] features = ["docs"] @@ -19,6 +25,6 @@ base64 = "0.13.0" byteorder = "1.4.3" image = { version = "0.23.14", optional = true } qrcode = { version = "0.12.0", default-features = false } -rqrr = { version = "0.3.2" , optional = true } -ruma-identifiers = "0.19.1" -thiserror = "1.0.24" +rqrr = { version = "0.3.2", optional = true } +ruma-identifiers = "0.19.3" +thiserror = "1.0.25" diff --git a/matrix_qrcode/README.md b/matrix_qrcode/README.md new file mode 100644 index 00000000..9da0daeb --- /dev/null +++ b/matrix_qrcode/README.md @@ -0,0 +1,62 @@ +[![Build Status](https://img.shields.io/travis/matrix-org/matrix-rust-sdk.svg?style=flat-square)](https://travis-ci.org/matrix-org/matrix-rust-sdk) +[![codecov](https://img.shields.io/codecov/c/github/matrix-org/matrix-rust-sdk/master.svg?style=flat-square)](https://codecov.io/gh/matrix-org/matrix-rust-sdk) +[![License](https://img.shields.io/badge/License-Apache%202.0-yellowgreen.svg?style=flat-square)](https://opensource.org/licenses/Apache-2.0) +[![#matrix-rust-sdk](https://img.shields.io/badge/matrix-%23matrix--rust--sdk-blue?style=flat-square)](https://matrix.to/#/#matrix-rust-sdk:matrix.org) + +# matrix-qrcode + +**matrix-qrcode** is a crate to easily generate and parse QR codes for +interactive verification using [QR codes] in Matrix. + +[Matrix]: https://matrix.org/ +[Rust]: https://www.rust-lang.org/ +[QR codes]: https://spec.matrix.org/unstable/client-server-api/#qr-codes + +## Usage + +This is probably not the crate you are looking for, it's used internally in the +matrix-rust-sdk. + +If you still want to play with QR codes, here are a couple of helpful examples. + + +### Decode an image + +```rust +use image; +use matrix_qrcode::{QrVerificationData, DecodingError}; + +fn main() -> Result<(), DecodingError> { + let image = image::open("/path/to/my/image.png").unwrap(); + let result = QrVerificationData::from_image(image)?; + + Ok(()) +} +``` + +### Encode into a QR code + +```rust +use matrix_qrcode::{QrVerificationData, DecodingError}; +use image::Luma; + +fn main() -> Result<(), DecodingError> { + let data = b"MATRIX\ + \x02\x02\x00\x07\ + FLOW_ID\ + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\ + BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\ + SHARED_SECRET"; + + let data = QrVerificationData::from_bytes(data)?; + let encoded = data.to_qr_code().unwrap(); + let image = encoded.render::>().build(); + + Ok(()) +} +``` + + +## License + +[Apache-2.0](https://www.apache.org/licenses/LICENSE-2.0) diff --git a/matrix_qrcode/src/lib.rs b/matrix_qrcode/src/lib.rs index 91e60735..bf8f18d5 100644 --- a/matrix_qrcode/src/lib.rs +++ b/matrix_qrcode/src/lib.rs @@ -20,12 +20,12 @@ //! [spec]: https://spec.matrix.org/unstable/client-server-api/#qr-code-format //! //! ```no_run -//! # use matrix_qrcode::{QrVerification, DecodingError}; +//! # use matrix_qrcode::{QrVerificationData, DecodingError}; //! # fn main() -> Result<(), DecodingError> { //! use image; //! //! let image = image::open("/path/to/my/image.png").unwrap(); -//! let result = QrVerification::from_image(image)?; +//! let result = QrVerificationData::from_image(image)?; //! # Ok(()) //! # } //! ``` @@ -55,7 +55,7 @@ pub use qrcode; #[cfg_attr(feature = "docs", doc(cfg(decode_image)))] pub use rqrr; pub use types::{ - QrVerification, SelfVerificationData, SelfVerificationNoMasterKey, VerificationData, + QrVerificationData, SelfVerificationData, SelfVerificationNoMasterKey, VerificationData, }; #[cfg(test)] @@ -70,7 +70,7 @@ mod test { #[cfg(feature = "decode_image")] use crate::utils::decode_qr; - use crate::{DecodingError, QrVerification}; + use crate::{DecodingError, QrVerificationData}; #[cfg(feature = "decode_image")] static VERIFICATION: &[u8; 4277] = include_bytes!("../data/verification.png"); @@ -92,9 +92,9 @@ mod test { fn decode_test() { let image = Cursor::new(VERIFICATION); let image = image::load(image, ImageFormat::Png).unwrap().to_luma8(); - let result = QrVerification::try_from(image).unwrap(); + let result = QrVerificationData::try_from(image).unwrap(); - assert!(matches!(result, QrVerification::Verification(_))); + assert!(matches!(result, QrVerificationData::Verification(_))); } #[test] @@ -102,18 +102,18 @@ mod test { fn decode_encode_cycle() { let image = Cursor::new(VERIFICATION); let image = image::load(image, ImageFormat::Png).unwrap(); - let result = QrVerification::from_image(image).unwrap(); + let result = QrVerificationData::from_image(image).unwrap(); - assert!(matches!(result, QrVerification::Verification(_))); + assert!(matches!(result, QrVerificationData::Verification(_))); let encoded = result.to_qr_code().unwrap(); let image = encoded.render::>().build(); - let second_result = QrVerification::try_from(image).unwrap(); + let second_result = QrVerificationData::try_from(image).unwrap(); assert_eq!(result, second_result); let bytes = result.to_bytes().unwrap(); - let third_result = QrVerification::from_bytes(bytes).unwrap(); + let third_result = QrVerificationData::from_bytes(bytes).unwrap(); assert_eq!(result, third_result); } @@ -123,18 +123,18 @@ mod test { fn decode_encode_cycle_self() { let image = Cursor::new(SELF_VERIFICATION); let image = image::load(image, ImageFormat::Png).unwrap(); - let result = QrVerification::try_from(image).unwrap(); + let result = QrVerificationData::try_from(image).unwrap(); - assert!(matches!(result, QrVerification::SelfVerification(_))); + assert!(matches!(result, QrVerificationData::SelfVerification(_))); let encoded = result.to_qr_code().unwrap(); let image = encoded.render::>().build(); - let second_result = QrVerification::from_luma(image).unwrap(); + let second_result = QrVerificationData::from_luma(image).unwrap(); assert_eq!(result, second_result); let bytes = result.to_bytes().unwrap(); - let third_result = QrVerification::from_bytes(bytes).unwrap(); + let third_result = QrVerificationData::from_bytes(bytes).unwrap(); assert_eq!(result, third_result); } @@ -144,18 +144,18 @@ mod test { fn decode_encode_cycle_self_no_master() { let image = Cursor::new(SELF_NO_MASTER); let image = image::load(image, ImageFormat::Png).unwrap(); - let result = QrVerification::from_image(image).unwrap(); + let result = QrVerificationData::from_image(image).unwrap(); - assert!(matches!(result, QrVerification::SelfVerificationNoMasterKey(_))); + assert!(matches!(result, QrVerificationData::SelfVerificationNoMasterKey(_))); let encoded = result.to_qr_code().unwrap(); let image = encoded.render::>().build(); - let second_result = QrVerification::try_from(image).unwrap(); + let second_result = QrVerificationData::try_from(image).unwrap(); assert_eq!(result, second_result); let bytes = result.to_bytes().unwrap(); - let third_result = QrVerification::try_from(bytes).unwrap(); + let third_result = QrVerificationData::try_from(bytes).unwrap(); assert_eq!(result, third_result); } @@ -165,35 +165,35 @@ mod test { fn decode_invalid_qr() { let qr = QrCode::new(b"NonMatrixCode").expect("Can't build a simple QR code"); let image = qr.render::>().build(); - let result = QrVerification::try_from(image); + let result = QrVerificationData::try_from(image); assert!(matches!(result, Err(DecodingError::Header))) } #[test] fn decode_invalid_header() { let data = b"NonMatrixCode"; - let result = QrVerification::from_bytes(data); + let result = QrVerificationData::from_bytes(data); assert!(matches!(result, Err(DecodingError::Header))) } #[test] fn decode_invalid_mode() { let data = b"MATRIX\x02\x03"; - let result = QrVerification::from_bytes(data); + let result = QrVerificationData::from_bytes(data); assert!(matches!(result, Err(DecodingError::Mode(3)))) } #[test] fn decode_invalid_version() { let data = b"MATRIX\x01\x03"; - let result = QrVerification::from_bytes(data); + let result = QrVerificationData::from_bytes(data); assert!(matches!(result, Err(DecodingError::Version(1)))) } #[test] fn decode_missing_data() { let data = b"MATRIX\x02\x02"; - let result = QrVerification::from_bytes(data); + let result = QrVerificationData::from_bytes(data); assert!(matches!(result, Err(DecodingError::Read(_)))) } @@ -206,7 +206,7 @@ mod test { BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\ SECRET"; - let result = QrVerification::from_bytes(data); + let result = QrVerificationData::from_bytes(data); assert!(matches!(result, Err(DecodingError::SharedSecret(_)))) } @@ -219,7 +219,7 @@ mod test { BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\ SECRETISLONGENOUGH"; - let result = QrVerification::from_bytes(data); + let result = QrVerificationData::from_bytes(data); assert!(matches!(result, Err(DecodingError::Identifier(_)))) } } diff --git a/matrix_qrcode/src/types.rs b/matrix_qrcode/src/types.rs index 915c901a..03e38086 100644 --- a/matrix_qrcode/src/types.rs +++ b/matrix_qrcode/src/types.rs @@ -33,7 +33,7 @@ use crate::{ /// An enum representing the different modes a QR verification can be in. #[derive(Clone, Debug, PartialEq)] -pub enum QrVerification { +pub enum QrVerificationData { /// The QR verification is verifying another user Verification(VerificationData), /// The QR verification is self-verifying and the current device trusts or @@ -46,7 +46,7 @@ pub enum QrVerification { #[cfg(feature = "decode_image")] #[cfg_attr(feature = "docs", doc(cfg(decode_image)))] -impl TryFrom for QrVerification { +impl TryFrom for QrVerificationData { type Error = DecodingError; fn try_from(image: DynamicImage) -> Result { @@ -56,7 +56,7 @@ impl TryFrom for QrVerification { #[cfg(feature = "decode_image")] #[cfg_attr(feature = "docs", doc(cfg(decode_image)))] -impl TryFrom, Vec>> for QrVerification { +impl TryFrom, Vec>> for QrVerificationData { type Error = DecodingError; fn try_from(image: ImageBuffer, Vec>) -> Result { @@ -64,7 +64,7 @@ impl TryFrom, Vec>> for QrVerification { } } -impl TryFrom<&[u8]> for QrVerification { +impl TryFrom<&[u8]> for QrVerificationData { type Error = DecodingError; fn try_from(value: &[u8]) -> Result { @@ -72,7 +72,7 @@ impl TryFrom<&[u8]> for QrVerification { } } -impl TryFrom> for QrVerification { +impl TryFrom> for QrVerificationData { type Error = DecodingError; fn try_from(value: Vec) -> Result { @@ -80,8 +80,8 @@ impl TryFrom> for QrVerification { } } -impl QrVerification { - /// Decode and parse an image of a QR code into a `QrVerification` +impl QrVerificationData { + /// Decode and parse an image of a QR code into a `QrVerificationData` /// /// The image will be converted into a grey scale image before decoding is /// attempted @@ -92,12 +92,12 @@ impl QrVerification { /// /// # Example /// ```no_run - /// # use matrix_qrcode::{QrVerification, DecodingError}; + /// # use matrix_qrcode::{QrVerificationData, DecodingError}; /// # fn main() -> Result<(), DecodingError> { /// use image; /// /// let image = image::open("/path/to/my/image.png").unwrap(); - /// let result = QrVerification::from_image(image)?; + /// let result = QrVerificationData::from_image(image)?; /// # Ok(()) /// # } /// ``` @@ -109,7 +109,7 @@ impl QrVerification { } /// Decode and parse an grey scale image of a QR code into a - /// `QrVerification` + /// `QrVerificationData` /// /// # Arguments /// @@ -117,13 +117,13 @@ impl QrVerification { /// /// # Example /// ```no_run - /// # use matrix_qrcode::{QrVerification, DecodingError}; + /// # use matrix_qrcode::{QrVerificationData, DecodingError}; /// # fn main() -> Result<(), DecodingError> { /// use image; /// /// let image = image::open("/path/to/my/image.png").unwrap(); /// let image = image.to_luma8(); - /// let result = QrVerification::from_luma(image)?; + /// let result = QrVerificationData::from_luma(image)?; /// # Ok(()) /// # } /// ``` @@ -134,7 +134,7 @@ impl QrVerification { } /// Parse the decoded payload of a QR code in byte slice form as a - /// `QrVerification` + /// `QrVerificationData` /// /// This method is useful if you would like to do your own custom QR code /// decoding. @@ -145,7 +145,7 @@ impl QrVerification { /// /// # Example /// ``` - /// # use matrix_qrcode::{QrVerification, DecodingError}; + /// # use matrix_qrcode::{QrVerificationData, DecodingError}; /// # fn main() -> Result<(), DecodingError> { /// let data = b"MATRIX\ /// \x02\x02\x00\x07\ @@ -154,7 +154,7 @@ impl QrVerification { /// BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\ /// SHARED_SECRET"; /// - /// let result = QrVerification::from_bytes(data)?; + /// let result = QrVerificationData::from_bytes(data)?; /// # Ok(()) /// # } /// ``` @@ -162,9 +162,9 @@ impl QrVerification { Self::decode_bytes(bytes) } - /// Encode the `QrVerification` into a `QrCode`. + /// Encode the `QrVerificationData` into a `QrCode`. /// - /// This method turns the `QrVerification` into a QR code that can be + /// This method turns the `QrVerificationData` into a QR code that can be /// rendered and presented to be scanned. /// /// The encoding can fail if the data doesn't fit into a QR code or if the @@ -173,7 +173,7 @@ impl QrVerification { /// /// # Example /// ``` - /// # use matrix_qrcode::{QrVerification, DecodingError}; + /// # use matrix_qrcode::{QrVerificationData, DecodingError}; /// # fn main() -> Result<(), DecodingError> { /// let data = b"MATRIX\ /// \x02\x02\x00\x07\ @@ -182,28 +182,28 @@ impl QrVerification { /// BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\ /// SHARED_SECRET"; /// - /// let result = QrVerification::from_bytes(data)?; + /// let result = QrVerificationData::from_bytes(data)?; /// let encoded = result.to_qr_code().unwrap(); /// # Ok(()) /// # } /// ``` pub fn to_qr_code(&self) -> Result { match self { - QrVerification::Verification(v) => v.to_qr_code(), - QrVerification::SelfVerification(v) => v.to_qr_code(), - QrVerification::SelfVerificationNoMasterKey(v) => v.to_qr_code(), + QrVerificationData::Verification(v) => v.to_qr_code(), + QrVerificationData::SelfVerification(v) => v.to_qr_code(), + QrVerificationData::SelfVerificationNoMasterKey(v) => v.to_qr_code(), } } - /// Encode the `QrVerification` into a vector of bytes that can be encoded - /// as a QR code. + /// Encode the `QrVerificationData` into a vector of bytes that can be + /// encoded as a QR code. /// /// The encoding can fail if the identity keys that should be encoded are /// not valid base64. /// /// # Example /// ``` - /// # use matrix_qrcode::{QrVerification, DecodingError}; + /// # use matrix_qrcode::{QrVerificationData, DecodingError}; /// # fn main() -> Result<(), DecodingError> { /// let data = b"MATRIX\ /// \x02\x02\x00\x07\ @@ -212,7 +212,7 @@ impl QrVerification { /// BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\ /// SHARED_SECRET"; /// - /// let result = QrVerification::from_bytes(data)?; + /// let result = QrVerificationData::from_bytes(data)?; /// let encoded = result.to_bytes().unwrap(); /// /// assert_eq!(data.as_ref(), encoded.as_slice()); @@ -221,9 +221,9 @@ impl QrVerification { /// ``` pub fn to_bytes(&self) -> Result, EncodingError> { match self { - QrVerification::Verification(v) => v.to_bytes(), - QrVerification::SelfVerification(v) => v.to_bytes(), - QrVerification::SelfVerificationNoMasterKey(v) => v.to_bytes(), + QrVerificationData::Verification(v) => v.to_bytes(), + QrVerificationData::SelfVerification(v) => v.to_bytes(), + QrVerificationData::SelfVerificationNoMasterKey(v) => v.to_bytes(), } } @@ -289,13 +289,13 @@ impl QrVerification { return Err(DecodingError::SharedSecret(shared_secret.len())); } - QrVerification::new(mode, flow_id, first_key, second_key, shared_secret) + QrVerificationData::new(mode, flow_id, first_key, second_key, shared_secret) } /// Decode the given image of an QR code and if we find a valid code, try to /// decode it as a `QrVerification`. #[cfg(feature = "decode_image")] - fn decode(image: ImageBuffer, Vec>) -> Result { + fn decode(image: ImageBuffer, Vec>) -> Result { let decoded = decode_qr(image)?; Self::decode_bytes(decoded) } @@ -328,41 +328,41 @@ impl QrVerification { } } - /// Get the flow id for this `QrVerification`. + /// Get the flow id for this `QrVerificationData`. /// /// This represents the ID as a string even if it is a `EventId`. pub fn flow_id(&self) -> &str { match self { - QrVerification::Verification(v) => v.event_id.as_str(), - QrVerification::SelfVerification(v) => &v.transaction_id, - QrVerification::SelfVerificationNoMasterKey(v) => &v.transaction_id, + QrVerificationData::Verification(v) => v.event_id.as_str(), + QrVerificationData::SelfVerification(v) => &v.transaction_id, + QrVerificationData::SelfVerificationNoMasterKey(v) => &v.transaction_id, } } - /// Get the first key of this `QrVerification`. + /// Get the first key of this `QrVerificationData`. pub fn first_key(&self) -> &str { match self { - QrVerification::Verification(v) => &v.first_master_key, - QrVerification::SelfVerification(v) => &v.master_key, - QrVerification::SelfVerificationNoMasterKey(v) => &v.device_key, + QrVerificationData::Verification(v) => &v.first_master_key, + QrVerificationData::SelfVerification(v) => &v.master_key, + QrVerificationData::SelfVerificationNoMasterKey(v) => &v.device_key, } } - /// Get the second key of this `QrVerification`. + /// Get the second key of this `QrVerificationData`. pub fn second_key(&self) -> &str { match self { - QrVerification::Verification(v) => &v.second_master_key, - QrVerification::SelfVerification(v) => &v.device_key, - QrVerification::SelfVerificationNoMasterKey(v) => &v.master_key, + QrVerificationData::Verification(v) => &v.second_master_key, + QrVerificationData::SelfVerification(v) => &v.device_key, + QrVerificationData::SelfVerificationNoMasterKey(v) => &v.master_key, } } - /// Get the secret of this `QrVerification`. + /// Get the secret of this `QrVerificationData`. pub fn secret(&self) -> &str { match self { - QrVerification::Verification(v) => &v.shared_secret, - QrVerification::SelfVerification(v) => &v.shared_secret, - QrVerification::SelfVerificationNoMasterKey(v) => &v.shared_secret, + QrVerificationData::Verification(v) => &v.shared_secret, + QrVerificationData::SelfVerification(v) => &v.shared_secret, + QrVerificationData::SelfVerificationNoMasterKey(v) => &v.shared_secret, } } } @@ -412,7 +412,7 @@ impl VerificationData { /// /// # Example /// ``` - /// # use matrix_qrcode::{QrVerification, DecodingError}; + /// # use matrix_qrcode::{QrVerificationData, DecodingError}; /// # fn main() -> Result<(), DecodingError> { /// let data = b"MATRIX\ /// \x02\x00\x00\x0f\ @@ -421,8 +421,8 @@ impl VerificationData { /// BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\ /// SHARED_SECRET"; /// - /// let result = QrVerification::from_bytes(data)?; - /// if let QrVerification::Verification(decoded) = result { + /// let result = QrVerificationData::from_bytes(data)?; + /// if let QrVerificationData::Verification(decoded) = result { /// let encoded = decoded.to_bytes().unwrap(); /// assert_eq!(data.as_ref(), encoded.as_slice()); /// } else { @@ -459,7 +459,7 @@ impl VerificationData { } } -impl From for QrVerification { +impl From for QrVerificationData { fn from(data: VerificationData) -> Self { Self::Verification(data) } @@ -515,7 +515,7 @@ impl SelfVerificationData { /// /// # Example /// ``` - /// # use matrix_qrcode::{QrVerification, DecodingError}; + /// # use matrix_qrcode::{QrVerificationData, DecodingError}; /// # fn main() -> Result<(), DecodingError> { /// let data = b"MATRIX\ /// \x02\x01\x00\x06\ @@ -524,8 +524,8 @@ impl SelfVerificationData { /// BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\ /// SHARED_SECRET"; /// - /// let result = QrVerification::from_bytes(data)?; - /// if let QrVerification::SelfVerification(decoded) = result { + /// let result = QrVerificationData::from_bytes(data)?; + /// if let QrVerificationData::SelfVerification(decoded) = result { /// let encoded = decoded.to_bytes().unwrap(); /// assert_eq!(data.as_ref(), encoded.as_slice()); /// } else { @@ -562,7 +562,7 @@ impl SelfVerificationData { } } -impl From for QrVerification { +impl From for QrVerificationData { fn from(data: SelfVerificationData) -> Self { Self::SelfVerification(data) } @@ -618,7 +618,7 @@ impl SelfVerificationNoMasterKey { /// /// # Example /// ``` - /// # use matrix_qrcode::{QrVerification, DecodingError}; + /// # use matrix_qrcode::{QrVerificationData, DecodingError}; /// # fn main() -> Result<(), DecodingError> { /// let data = b"MATRIX\ /// \x02\x02\x00\x06\ @@ -627,8 +627,8 @@ impl SelfVerificationNoMasterKey { /// BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\ /// SHARED_SECRET"; /// - /// let result = QrVerification::from_bytes(data)?; - /// if let QrVerification::SelfVerificationNoMasterKey(decoded) = result { + /// let result = QrVerificationData::from_bytes(data)?; + /// if let QrVerificationData::SelfVerificationNoMasterKey(decoded) = result { /// let encoded = decoded.to_bytes().unwrap(); /// assert_eq!(data.as_ref(), encoded.as_slice()); /// } else { @@ -665,7 +665,7 @@ impl SelfVerificationNoMasterKey { } } -impl From for QrVerification { +impl From for QrVerificationData { fn from(data: SelfVerificationNoMasterKey) -> Self { Self::SelfVerificationNoMasterKey(data) } diff --git a/matrix_sdk/Cargo.toml b/matrix_sdk/Cargo.toml index f59c700e..70a5a8cf 100644 --- a/matrix_sdk/Cargo.toml +++ b/matrix_sdk/Cargo.toml @@ -8,7 +8,7 @@ license = "Apache-2.0" name = "matrix-sdk" readme = "README.md" repository = "https://github.com/matrix-org/matrix-rust-sdk" -version = "0.2.0" +version = "0.3.0" [package.metadata.docs.rs] features = ["docs"] @@ -32,39 +32,39 @@ docs = ["encryption", "sled_cryptostore", "sled_state_store", "sso_login"] [dependencies] dashmap = "4.0.2" -futures = "0.3.12" -http = "0.2.3" -serde_json = "1.0.61" -thiserror = "1.0.23" -tracing = "0.1.22" -url = "2.2.0" -zeroize = "1.2.0" +futures = "0.3.15" +http = "0.2.4" +serde_json = "1.0.64" +thiserror = "1.0.25" +tracing = "0.1.26" +url = "2.2.2" +zeroize = "1.3.0" mime = "0.3.16" -rand = { version = "0.8.2", optional = true } +rand = { version = "0.8.4", optional = true } bytes = "1.0.1" -matrix-sdk-common = { version = "0.2.0", path = "../matrix_sdk_common" } +matrix-sdk-common = { version = "0.3.0", path = "../matrix_sdk_common" } [dependencies.matrix-sdk-base] -version = "0.2.0" +version = "0.3.0" path = "../matrix_sdk_base" default_features = false [dependencies.reqwest] -version = "0.11.0" +version = "0.11.3" default_features = false [dependencies.ruma] -version = "0.1.2" +version = "0.2.0" features = ["client-api-c", "compat", "unstable-pre-spec"] [dependencies.tokio-stream] -version = "0.1.4" +version = "0.1.6" features = ["net"] optional = true [dependencies.warp] -version = "0.3.0" +version = "0.3.1" default-features = false optional = true @@ -73,7 +73,7 @@ version = "0.3.0" features = ["tokio"] [dependencies.tracing-futures] -version = "0.2.4" +version = "0.2.5" default-features = false features = ["std", "std-future"] @@ -81,7 +81,7 @@ features = ["std", "std-future"] futures-timer = "3.0.2" [target.'cfg(not(target_arch = "wasm32"))'.dependencies.tokio] -version = "1.1.0" +version = "1.7.1" default-features = false features = ["fs", "rt"] @@ -90,16 +90,15 @@ version = "3.0.2" features = ["wasm-bindgen"] [dev-dependencies] -dirs = "3.0.1" -matrix-sdk-test = { version = "0.2.0", path = "../matrix_sdk_test" } +dirs = "3.0.2" matches = "0.1.8" -tokio = { version = "1.1.0", default-features = false, features = ["rt-multi-thread", "macros"] } -serde_json = "1.0.61" -tracing-subscriber = "0.2.15" +matrix-sdk-test = { version = "0.3.0", path = "../matrix_sdk_test" } +tokio = { version = "1.7.1", default-features = false, features = ["rt-multi-thread", "macros"] } +serde_json = "1.0.64" +tracing-subscriber = "0.2.18" tempfile = "3.2.0" -mockito = "0.29.0" +mockito = "0.30.0" lazy_static = "1.4.0" -matrix-sdk-common = { version = "0.2.0", path = "../matrix_sdk_common" } [[example]] name = "emoji_verification" diff --git a/matrix_sdk/examples/autojoin.rs b/matrix_sdk/examples/autojoin.rs index f2ac3c1b..6838b7c1 100644 --- a/matrix_sdk/examples/autojoin.rs +++ b/matrix_sdk/examples/autojoin.rs @@ -1,9 +1,9 @@ use std::{env, process::exit}; use matrix_sdk::{ - self, async_trait, - events::{room::member::MemberEventContent, StrippedStateEvent}, + async_trait, room::Room, + ruma::events::{room::member::MemberEventContent, StrippedStateEvent}, Client, ClientConfig, EventHandler, SyncSettings, }; use tokio::time::{sleep, Duration}; diff --git a/matrix_sdk/examples/command_bot.rs b/matrix_sdk/examples/command_bot.rs index d6c00fda..90f00e38 100644 --- a/matrix_sdk/examples/command_bot.rs +++ b/matrix_sdk/examples/command_bot.rs @@ -1,12 +1,12 @@ use std::{env, process::exit}; use matrix_sdk::{ - self, async_trait, - events::{ + async_trait, + room::Room, + ruma::events::{ room::message::{MessageEventContent, MessageType, TextMessageEventContent}, AnyMessageEventContent, SyncMessageEvent, }, - room::Room, Client, ClientConfig, EventHandler, SyncSettings, }; use url::Url; diff --git a/matrix_sdk/examples/cross_signing_bootstrap.rs b/matrix_sdk/examples/cross_signing_bootstrap.rs index 107cfc58..cb6a5c74 100644 --- a/matrix_sdk/examples/cross_signing_bootstrap.rs +++ b/matrix_sdk/examples/cross_signing_bootstrap.rs @@ -6,7 +6,8 @@ use std::{ }; use matrix_sdk::{ - self, api::r0::uiaa::AuthData, identifiers::UserId, Client, LoopCtrl, SyncSettings, + ruma::{api::client::r0::uiaa::AuthData, UserId}, + Client, LoopCtrl, SyncSettings, }; use serde_json::json; use url::Url; diff --git a/matrix_sdk/examples/emoji_verification.rs b/matrix_sdk/examples/emoji_verification.rs index 72e8b7cd..1307d9a2 100644 --- a/matrix_sdk/examples/emoji_verification.rs +++ b/matrix_sdk/examples/emoji_verification.rs @@ -9,13 +9,18 @@ use std::{ use matrix_sdk::{ self, - events::{room::message::MessageType, AnySyncMessageEvent, AnySyncRoomEvent, AnyToDeviceEvent}, - identifiers::UserId, - Client, LoopCtrl, Sas, SyncSettings, + ruma::{ + events::{ + room::message::MessageType, AnySyncMessageEvent, AnySyncRoomEvent, AnyToDeviceEvent, + }, + UserId, + }, + verification::{SasVerification, Verification}, + Client, LoopCtrl, SyncSettings, }; use url::Url; -async fn wait_for_confirmation(client: Client, sas: Sas) { +async fn wait_for_confirmation(client: Client, sas: SasVerification) { println!("Does the emoji match: {:?}", sas.emoji()); let mut input = String::new(); @@ -34,7 +39,7 @@ async fn wait_for_confirmation(client: Client, sas: Sas) { } } -fn print_result(sas: &Sas) { +fn print_result(sas: &SasVerification) { let device = sas.other_device(); println!( @@ -53,7 +58,7 @@ async fn print_devices(user_id: &UserId, client: &Client) { " {:<10} {:<30} {:<}", device.device_id(), device.display_name().as_deref().unwrap_or_default(), - device.is_trusted() + device.verified() ); } } @@ -80,37 +85,35 @@ async fn login( for event in response.to_device.events.iter().filter_map(|e| e.deserialize().ok()) { match event { AnyToDeviceEvent::KeyVerificationStart(e) => { - let sas = client - .get_verification(&e.content.transaction_id) - .await - .expect("Sas object wasn't created"); - println!( - "Starting verification with {} {}", - &sas.other_device().user_id(), - &sas.other_device().device_id() - ); - print_devices(&e.sender, client).await; - sas.accept().await.unwrap(); + if let Some(Verification::SasV1(sas)) = + client.get_verification(&e.sender, &e.content.transaction_id).await + { + println!( + "Starting verification with {} {}", + &sas.other_device().user_id(), + &sas.other_device().device_id() + ); + print_devices(&e.sender, client).await; + sas.accept().await.unwrap(); + } } AnyToDeviceEvent::KeyVerificationKey(e) => { - let sas = client - .get_verification(&e.content.transaction_id) - .await - .expect("Sas object wasn't created"); - - tokio::spawn(wait_for_confirmation((*client).clone(), sas)); + if let Some(Verification::SasV1(sas)) = + client.get_verification(&e.sender, &e.content.transaction_id).await + { + tokio::spawn(wait_for_confirmation((*client).clone(), sas)); + } } AnyToDeviceEvent::KeyVerificationMac(e) => { - let sas = client - .get_verification(&e.content.transaction_id) - .await - .expect("Sas object wasn't created"); - - if sas.is_done() { - print_result(&sas); - print_devices(&e.sender, client).await; + if let Some(Verification::SasV1(sas)) = + client.get_verification(&e.sender, &e.content.transaction_id).await + { + if sas.is_done() { + print_result(&sas); + print_devices(&e.sender, client).await; + } } } @@ -129,7 +132,7 @@ async fn login( if let MessageType::VerificationRequest(_) = &m.content.msgtype { let request = client - .get_verification_request(&m.event_id) + .get_verification_request(&m.sender, &m.event_id) .await .expect("Request object wasn't created"); @@ -140,22 +143,28 @@ async fn login( } } AnySyncMessageEvent::KeyVerificationKey(e) => { - let sas = client - .get_verification(e.content.relation.event_id.as_str()) + if let Some(Verification::SasV1(sas)) = client + .get_verification( + &e.sender, + e.content.relates_to.event_id.as_str(), + ) .await - .expect("Sas object wasn't created"); - - tokio::spawn(wait_for_confirmation((*client).clone(), sas)); + { + tokio::spawn(wait_for_confirmation((*client).clone(), sas)); + } } AnySyncMessageEvent::KeyVerificationMac(e) => { - let sas = client - .get_verification(e.content.relation.event_id.as_str()) + if let Some(Verification::SasV1(sas)) = client + .get_verification( + &e.sender, + e.content.relates_to.event_id.as_str(), + ) .await - .expect("Sas object wasn't created"); - - if sas.is_done() { - print_result(&sas); - print_devices(&e.sender, client).await; + { + if sas.is_done() { + print_result(&sas); + print_devices(&e.sender, client).await; + } } } _ => (), diff --git a/matrix_sdk/examples/get_profiles.rs b/matrix_sdk/examples/get_profiles.rs index 1e8f5719..edbfcb1c 100644 --- a/matrix_sdk/examples/get_profiles.rs +++ b/matrix_sdk/examples/get_profiles.rs @@ -1,9 +1,7 @@ use std::{convert::TryFrom, env, process::exit}; use matrix_sdk::{ - self, - api::r0::profile, - identifiers::{MxcUri, UserId}, + ruma::{api::client::r0::profile, MxcUri, UserId}, Client, Result as MatrixResult, }; use url::Url; diff --git a/matrix_sdk/examples/image_bot.rs b/matrix_sdk/examples/image_bot.rs index 4408e1c3..7df95ffd 100644 --- a/matrix_sdk/examples/image_bot.rs +++ b/matrix_sdk/examples/image_bot.rs @@ -9,11 +9,11 @@ use std::{ use matrix_sdk::{ self, async_trait, - events::{ + room::Room, + ruma::events::{ room::message::{MessageEventContent, MessageType, TextMessageEventContent}, SyncMessageEvent, }, - room::Room, Client, EventHandler, SyncSettings, }; use tokio::sync::Mutex; diff --git a/matrix_sdk/examples/login.rs b/matrix_sdk/examples/login.rs index 31a71acd..fa6ab054 100644 --- a/matrix_sdk/examples/login.rs +++ b/matrix_sdk/examples/login.rs @@ -2,11 +2,11 @@ use std::{env, process::exit}; use matrix_sdk::{ self, async_trait, - events::{ + room::Room, + ruma::events::{ room::message::{MessageEventContent, MessageType, TextMessageEventContent}, SyncMessageEvent, }, - room::Room, Client, EventHandler, SyncSettings, }; use url::Url; diff --git a/matrix_sdk/examples/wasm_command_bot/Cargo.toml b/matrix_sdk/examples/wasm_command_bot/Cargo.toml index 91ad579d..0e49bf55 100644 --- a/matrix_sdk/examples/wasm_command_bot/Cargo.toml +++ b/matrix_sdk/examples/wasm_command_bot/Cargo.toml @@ -10,11 +10,11 @@ edition = "2018" crate-type = ["cdylib"] [dependencies] -url = "2.2.1" -wasm-bindgen = { version = "0.2.72", features = ["serde-serialize"] } -wasm-bindgen-futures = "0.4.22" +url = "2.2.2" +wasm-bindgen = { version = "0.2.74", features = ["serde-serialize"] } +wasm-bindgen-futures = "0.4.24" console_error_panic_hook = "0.1.6" -web-sys = { version = "0.3.49", features = ["console"] } +web-sys = { version = "0.3.51", features = ["console"] } [dependencies.matrix-sdk] path = "../.." diff --git a/matrix_sdk/examples/wasm_command_bot/src/lib.rs b/matrix_sdk/examples/wasm_command_bot/src/lib.rs index 7f4abbb4..ddc6744b 100644 --- a/matrix_sdk/examples/wasm_command_bot/src/lib.rs +++ b/matrix_sdk/examples/wasm_command_bot/src/lib.rs @@ -1,10 +1,12 @@ use matrix_sdk::{ deserialized_responses::SyncResponse, - events::{ - room::message::{MessageEventContent, MessageType, TextMessageEventContent}, - AnyMessageEventContent, AnySyncMessageEvent, AnySyncRoomEvent, SyncMessageEvent, + ruma::{ + events::{ + room::message::{MessageEventContent, MessageType, TextMessageEventContent}, + AnyMessageEventContent, AnySyncMessageEvent, AnySyncRoomEvent, SyncMessageEvent, + }, + RoomId, }, - identifiers::RoomId, Client, LoopCtrl, SyncSettings, }; use url::Url; @@ -58,7 +60,9 @@ impl WasmBot { for (room_id, room) in response.rooms.join { for event in room.timeline.events { - if let Ok(AnySyncRoomEvent::Message(AnySyncMessageEvent::RoomMessage(ev))) = event.event.deserialize() { + if let Ok(AnySyncRoomEvent::Message(AnySyncMessageEvent::RoomMessage(ev))) = + event.event.deserialize() + { self.on_room_message(&room_id, &ev).await } } @@ -79,19 +83,14 @@ pub async fn run() -> Result { let homeserver_url = Url::parse(&homeserver_url).unwrap(); let client = Client::new(homeserver_url).unwrap(); - client - .login(username, password, None, Some("rust-sdk-wasm")) - .await - .unwrap(); + client.login(username, password, None, Some("rust-sdk-wasm")).await.unwrap(); let bot = WasmBot(client.clone()); client.sync_once(SyncSettings::default()).await.unwrap(); let settings = SyncSettings::default().token(client.sync_token().await.unwrap()); - client - .sync_with_callback(settings, |response| bot.on_sync_response(response)) - .await; + client.sync_with_callback(settings, |response| bot.on_sync_response(response)).await; Ok(JsValue::NULL) } diff --git a/matrix_sdk/src/client.rs b/matrix_sdk/src/client.rs index 70ce5c33..ffb84b30 100644 --- a/matrix_sdk/src/client.rs +++ b/matrix_sdk/src/client.rs @@ -13,11 +13,12 @@ // See the License for the specific language governing permissions and // limitations under the License. +#[cfg(all(feature = "encryption", not(target_arch = "wasm32")))] +use std::path::PathBuf; #[cfg(feature = "encryption")] use std::{ collections::BTreeMap, io::{Cursor, Write}, - path::PathBuf, }; #[cfg(feature = "sso_login")] use std::{ @@ -39,10 +40,12 @@ use futures_timer::Delay as sleep; use http::HeaderValue; #[cfg(feature = "sso_login")] use http::Response; +#[cfg(all(feature = "encryption", not(target_arch = "wasm32")))] +use matrix_sdk_base::crypto::{decrypt_key_export, encrypt_key_export, olm::InboundGroupSession}; #[cfg(feature = "encryption")] use matrix_sdk_base::crypto::{ - decrypt_key_export, encrypt_key_export, olm::InboundGroupSession, store::CryptoStoreError, - AttachmentDecryptor, OutgoingRequests, RoomMessageRequest, ToDeviceRequest, + store::CryptoStoreError, AttachmentDecryptor, OutgoingRequests, RoomMessageRequest, + ToDeviceRequest, }; use matrix_sdk_base::{ deserialized_responses::SyncResponse, @@ -53,7 +56,7 @@ use mime::{self, Mime}; #[cfg(feature = "sso_login")] use rand::{thread_rng, Rng}; use reqwest::header::InvalidHeaderValue; -use ruma::{api::SendAccessToken, events::AnyMessageEventContent, identifiers::MxcUri}; +use ruma::{api::SendAccessToken, events::AnyMessageEventContent, MxcUri}; #[cfg(feature = "sso_login")] use tokio::{net::TcpListener, sync::oneshot}; #[cfg(feature = "sso_login")] @@ -64,7 +67,7 @@ use tracing::{error, info, instrument}; use url::Url; #[cfg(feature = "sso_login")] use warp::Filter; -#[cfg(feature = "encryption")] +#[cfg(all(feature = "encryption", not(target_arch = "wasm32")))] use zeroize::Zeroizing; /// Enum controlling if a loop running callbacks should continue or abort. @@ -126,8 +129,8 @@ use ruma::{ #[cfg(feature = "encryption")] use crate::{ device::{Device, UserDevices}, - sas::Sas, - verification_request::VerificationRequest, + error::RoomKeyImportError, + verification::{QrVerification, SasVerification, Verification, VerificationRequest}, }; use crate::{ error::HttpError, @@ -501,7 +504,7 @@ impl RequestConfig { /// All outgoing http requests will have a GET query key-value appended with /// `user_id` being the key and the `user_id` from the `Session` being /// the value. Will error if there's no `Session`. This is called - /// [identity assertion] in the Matrix Appservice Spec + /// [identity assertion] in the Matrix Application Service Spec /// /// [identity assertion]: https://spec.matrix.org/unstable/application-service-api/#identity-assertion #[cfg(feature = "appservice")] @@ -572,7 +575,7 @@ impl Client { /// # Example /// ```no_run /// # use std::convert::TryFrom; - /// # use matrix_sdk::{Client, identifiers::UserId}; + /// # use matrix_sdk::{Client, ruma::UserId}; /// # use futures::executor::block_on; /// let alice = UserId::try_from("@alice:example.org").unwrap(); /// # block_on(async { @@ -781,20 +784,20 @@ impl Client { /// Gets the avatar of the owner of the client, if set. /// - /// Returns the avatar. No guarantee on the size of the image is given. - /// If no size is given the full-sized avatar will be returned. + /// Returns the avatar. + /// If a thumbnail is requested no guarantee on the size of the image is + /// given. /// /// # Arguments /// - /// * `width` - The desired width of the avatar. - /// - /// * `height` - The desired height of the avatar. + /// * `format` - The desired format of the avatar. /// /// # Example /// ```no_run /// # use futures::executor::block_on; /// # use matrix_sdk::Client; - /// # use matrix_sdk::identifiers::room_id; + /// # use matrix_sdk::ruma::room_id; + /// # use matrix_sdk::media::MediaFormat; /// # use url::Url; /// # let homeserver = Url::parse("http://example.com").unwrap(); /// # block_on(async { @@ -802,24 +805,15 @@ impl Client { /// let client = Client::new(homeserver).unwrap(); /// client.login(user, "password", None, None).await.unwrap(); /// - /// if let Some(avatar) = client.avatar(Some(96), Some(96)).await.unwrap() { + /// if let Some(avatar) = client.avatar(MediaFormat::File).await.unwrap() { /// std::fs::write("avatar.png", avatar); /// } /// # }) /// ``` - pub async fn avatar(&self, width: Option, height: Option) -> Result>> { - // TODO: try to offer the avatar from cache, requires avatar cache + pub async fn avatar(&self, format: MediaFormat) -> Result>> { if let Some(url) = self.avatar_url().await? { - if let (Some(width), Some(height)) = (width, height) { - let request = - get_content_thumbnail::Request::from_url(&url, width.into(), height.into())?; - let response = self.send(request, None).await?; - Ok(Some(response.file)) - } else { - let request = get_content::Request::from_url(&url)?; - let response = self.send(request, None).await?; - Ok(Some(response.file)) - } + let request = MediaRequest { media_type: MediaType::Uri(url), format }; + Ok(Some(self.get_media_content(&request, true).await?)) } else { Ok(None) } @@ -1011,8 +1005,7 @@ impl Client { /// ```no_run /// # use std::convert::TryFrom; /// # use matrix_sdk::Client; - /// # use matrix_sdk::identifiers::DeviceId; - /// # use matrix_sdk::assign; + /// # use matrix_sdk::ruma::{assign, DeviceId}; /// # use futures::executor::block_on; /// # use url::Url; /// # let homeserver = Url::parse("http://example.com").unwrap(); @@ -1270,8 +1263,7 @@ impl Client { /// ```no_run /// # use std::convert::TryFrom; /// # use matrix_sdk::Client; - /// # use matrix_sdk::identifiers::DeviceId; - /// # use matrix_sdk::assign; + /// # use matrix_sdk::ruma::{assign, DeviceId}; /// # use futures::executor::block_on; /// # use url::Url; /// # let homeserver = Url::parse("https://example.com").unwrap(); @@ -1350,10 +1342,13 @@ impl Client { /// ```no_run /// # use std::convert::TryFrom; /// # use matrix_sdk::Client; - /// # use matrix_sdk::api::r0::account::register::{Request as RegistrationRequest, RegistrationKind}; - /// # use matrix_sdk::api::r0::uiaa::AuthData; - /// # use matrix_sdk::identifiers::DeviceId; - /// # use matrix_sdk::assign; + /// # use matrix_sdk::ruma::{ + /// # api::client::r0::{ + /// # account::register::{Request as RegistrationRequest, RegistrationKind}, + /// # uiaa::AuthData, + /// # }, + /// # assign, DeviceId, + /// # }; /// # use futures::executor::block_on; /// # use url::Url; /// # let homeserver = Url::parse("http://example.com").unwrap(); @@ -1403,7 +1398,7 @@ impl Client { /// ```no_run /// # use matrix_sdk::{ /// # Client, SyncSettings, - /// # api::r0::{ + /// # ruma::api::client::r0::{ /// # filter::{ /// # FilterDefinition, LazyLoadOptions, RoomEventFilter, RoomFilter, /// # }, @@ -1547,7 +1542,10 @@ impl Client { /// # Examples /// ```no_run /// use matrix_sdk::Client; - /// # use matrix_sdk::api::r0::room::{create_room::Request as CreateRoomRequest, Visibility}; + /// # use matrix_sdk::ruma::api::client::r0::room::{ + /// # create_room::Request as CreateRoomRequest, + /// # Visibility, + /// # }; /// # use url::Url; /// /// # let homeserver = Url::parse("http://example.com").unwrap(); @@ -1580,9 +1578,11 @@ impl Client { /// ```no_run /// # use std::convert::TryFrom; /// # use matrix_sdk::Client; - /// # use matrix_sdk::directory::{Filter, RoomNetwork}; - /// # use matrix_sdk::api::r0::directory::get_public_rooms_filtered::Request as PublicRoomsFilterRequest; - /// # use matrix_sdk::assign; + /// # use matrix_sdk::ruma::{ + /// # api::client::r0::directory::get_public_rooms_filtered::Request as PublicRoomsFilterRequest, + /// # directory::{Filter, RoomNetwork}, + /// # assign, + /// # }; /// # use url::Url; /// # use futures::executor::block_on; /// # let homeserver = Url::parse("http://example.com").unwrap(); @@ -1633,7 +1633,7 @@ impl Client { /// /// ```no_run /// # use std::{path::PathBuf, fs::File, io::Read}; - /// # use matrix_sdk::{Client, identifiers::room_id}; + /// # use matrix_sdk::{Client, ruma::room_id}; /// # use url::Url; /// # use futures::executor::block_on; /// # use mime; @@ -1700,9 +1700,9 @@ impl Client { /// # use matrix_sdk::{Client, SyncSettings}; /// # use url::Url; /// # use futures::executor::block_on; - /// # use matrix_sdk::identifiers::room_id; + /// # use matrix_sdk::ruma::room_id; /// # use std::convert::TryFrom; - /// use matrix_sdk::events::{ + /// use matrix_sdk::ruma::events::{ /// AnyMessageEventContent, /// room::message::{MessageEventContent, TextMessageEventContent}, /// }; @@ -1762,8 +1762,7 @@ impl Client { /// # block_on(async { /// # let homeserver = Url::parse("http://localhost:8080").unwrap(); /// # let mut client = Client::new(homeserver).unwrap(); - /// use matrix_sdk::api::r0::profile; - /// use matrix_sdk::identifiers::user_id; + /// use matrix_sdk::ruma::{api::client::r0::profile, user_id}; /// /// // First construct the request you want to make /// // See https://docs.rs/ruma-client-api/latest/ruma_client_api/index.html @@ -1796,8 +1795,8 @@ impl Client { request: &ToDeviceRequest, ) -> Result { let txn_id_string = request.txn_id_string(); - let request = RumaToDeviceRequest::new( - request.event_type.clone(), + let request = RumaToDeviceRequest::new_raw( + request.event_type.as_str(), &txn_id_string, request.messages.clone(), ); @@ -1849,8 +1848,11 @@ impl Client { /// /// ```no_run /// # use matrix_sdk::{ - /// # api::r0::uiaa::{UiaaResponse, AuthData}, - /// # Client, SyncSettings, Error, FromHttpResponseError, ServerError, + /// # ruma::api::{ + /// # client::r0::uiaa::{UiaaResponse, AuthData}, + /// # error::{FromHttpResponseError, ServerError}, + /// # }, + /// # Client, Error, SyncSettings, /// # }; /// # use futures::executor::block_on; /// # use serde_json::json; @@ -1969,7 +1971,7 @@ impl Client { /// UI thread. /// /// ```no_run - /// # use matrix_sdk::events::{ + /// # use matrix_sdk::ruma::events::{ /// # room::message::{MessageEvent, MessageEventContent, TextMessageEventContent}, /// # }; /// # use std::sync::{Arc, RwLock}; @@ -2191,26 +2193,33 @@ impl Client { Ok(response) } - /// Get a `Sas` verification object with the given flow id. + /// Get a verification object with the given flow id. #[cfg(feature = "encryption")] #[cfg_attr(feature = "docs", doc(cfg(encryption)))] - pub async fn get_verification(&self, flow_id: &str) -> Option { - self.base_client - .get_verification(flow_id) - .await - .map(|sas| Sas { inner: sas, client: self.clone() }) + pub async fn get_verification(&self, user_id: &UserId, flow_id: &str) -> Option { + let olm = self.base_client.olm_machine().await?; + olm.get_verification(user_id, flow_id).map(|v| match v { + matrix_sdk_base::crypto::Verification::SasV1(s) => { + SasVerification { inner: s, client: self.clone() }.into() + } + matrix_sdk_base::crypto::Verification::QrV1(qr) => { + QrVerification { inner: qr, client: self.clone() }.into() + } + }) } - /// Get a `VerificationRequest` object with the given flow id. + /// Get a `VerificationRequest` object for the given user with the given + /// flow id. #[cfg(feature = "encryption")] #[cfg_attr(feature = "docs", doc(cfg(encryption)))] pub async fn get_verification_request( &self, + user_id: &UserId, flow_id: impl AsRef, ) -> Option { let olm = self.base_client.olm_machine().await?; - olm.get_verification_request(flow_id) + olm.get_verification_request(user_id, flow_id) .map(|r| VerificationRequest { inner: r, client: self.clone() }) } @@ -2231,7 +2240,7 @@ impl Client { /// /// ```no_run /// # use std::convert::TryFrom; - /// # use matrix_sdk::{Client, identifiers::UserId}; + /// # use matrix_sdk::{Client, ruma::UserId}; /// # use url::Url; /// # use futures::executor::block_on; /// # let alice = UserId::try_from("@alice:example.org").unwrap(); @@ -2243,7 +2252,7 @@ impl Client { /// .unwrap() /// .unwrap(); /// - /// println!("{:?}", device.is_trusted()); + /// println!("{:?}", device.verified()); /// /// let verification = device.start_verification().await.unwrap(); /// # }); @@ -2273,8 +2282,8 @@ impl Client { /// # Examples /// ```no_run /// # use std::{convert::TryFrom, collections::BTreeMap}; - /// # use matrix_sdk::{Client, identifiers::UserId}; - /// # use matrix_sdk::api::r0::uiaa::AuthData; + /// # use matrix_sdk::{Client, ruma::UserId}; + /// # use matrix_sdk::ruma::api::client::r0::uiaa::AuthData; /// # use url::Url; /// # use futures::executor::block_on; /// # use serde_json::json; @@ -2344,7 +2353,7 @@ impl Client { /// /// ```no_run /// # use std::convert::TryFrom; - /// # use matrix_sdk::{Client, identifiers::UserId}; + /// # use matrix_sdk::{Client, ruma::UserId}; /// # use url::Url; /// # use futures::executor::block_on; /// # let alice = UserId::try_from("@alice:example.org").unwrap(); @@ -2398,7 +2407,7 @@ impl Client { /// # use std::{path::PathBuf, time::Duration}; /// # use matrix_sdk::{ /// # Client, SyncSettings, - /// # identifiers::room_id, + /// # ruma::room_id, /// # }; /// # use futures::executor::block_on; /// # use url::Url; @@ -2422,8 +2431,7 @@ impl Client { /// .expect("Can't export keys."); /// # }); /// ``` - #[cfg(feature = "encryption")] - #[cfg(not(target_arch = "wasm32"))] + #[cfg(all(feature = "encryption", not(target_arch = "wasm32")))] #[cfg_attr(feature = "docs", doc(cfg(all(encryption, not(target_arch = "wasm32")))))] pub async fn export_keys( &self, @@ -2468,7 +2476,7 @@ impl Client { /// # use std::{path::PathBuf, time::Duration}; /// # use matrix_sdk::{ /// # Client, SyncSettings, - /// # identifiers::room_id, + /// # ruma::room_id, /// # }; /// # use futures::executor::block_on; /// # use url::Url; @@ -2482,11 +2490,14 @@ impl Client { /// .expect("Can't import keys"); /// # }); /// ``` - #[cfg(feature = "encryption")] - #[cfg(not(target_arch = "wasm32"))] + #[cfg(all(feature = "encryption", not(target_arch = "wasm32")))] #[cfg_attr(feature = "docs", doc(cfg(all(encryption, not(target_arch = "wasm32")))))] - pub async fn import_keys(&self, path: PathBuf, passphrase: &str) -> Result<(usize, usize)> { - let olm = self.base_client.olm_machine().await.ok_or(Error::AuthenticationRequired)?; + pub async fn import_keys( + &self, + path: PathBuf, + passphrase: &str, + ) -> StdResult<(usize, usize), RoomKeyImportError> { + let olm = self.base_client.olm_machine().await.ok_or(RoomKeyImportError::StoreClosed)?; let passphrase = Zeroizing::new(passphrase.to_owned()); let decrypt = move || { @@ -2495,8 +2506,7 @@ impl Client { }; let task = tokio::task::spawn_blocking(decrypt); - // TODO remove this unwrap. - let import = task.await.expect("Task join error").unwrap(); + let import = task.await.expect("Task join error")?; Ok(olm.import_keys(import, |_, _| {}).await?) } @@ -2704,6 +2714,23 @@ impl Client { let request = whoami::Request::new(); self.send(request, None).await } + + #[cfg(feature = "encryption")] + pub(crate) async fn send_verification_request( + &self, + request: matrix_sdk_base::crypto::OutgoingVerificationRequest, + ) -> Result<()> { + match request { + matrix_sdk_base::crypto::OutgoingVerificationRequest::ToDevice(t) => { + self.send_to_device(&t).await?; + } + matrix_sdk_base::crypto::OutgoingVerificationRequest::InRoom(r) => { + self.room_send_helper(&r).await?; + } + } + + Ok(()) + } } #[cfg(test)] diff --git a/matrix_sdk/src/device.rs b/matrix_sdk/src/device.rs index 37bf06dc..3d50a460 100644 --- a/matrix_sdk/src/device.rs +++ b/matrix_sdk/src/device.rs @@ -18,9 +18,13 @@ use matrix_sdk_base::crypto::{ store::CryptoStoreError, Device as BaseDevice, LocalTrust, ReadOnlyDevice, UserDevices as BaseUserDevices, }; -use ruma::{DeviceId, DeviceIdBox}; +use ruma::{events::key::verification::VerificationMethod, DeviceId, DeviceIdBox}; -use crate::{error::Result, Client, Sas}; +use crate::{ + error::Result, + verification::{SasVerification, VerificationRequest}, + Client, +}; #[derive(Clone, Debug)] /// A device represents a E2EE capable client of an user. @@ -43,11 +47,14 @@ impl Device { /// Returns a `Sas` object that represents the interactive verification /// flow. /// - /// # Example + /// This method has been deprecated in the spec and the + /// [`request_verification()`] method should be used instead. + /// + /// # Examples /// /// ```no_run /// # use std::convert::TryFrom; - /// # use matrix_sdk::{Client, identifiers::UserId}; + /// # use matrix_sdk::{Client, ruma::UserId}; /// # use url::Url; /// # use futures::executor::block_on; /// # let alice = UserId::try_from("@alice:example.org").unwrap(); @@ -62,16 +69,106 @@ impl Device { /// let verification = device.start_verification().await.unwrap(); /// # }); /// ``` - pub async fn start_verification(&self) -> Result { + /// + /// [`request_verification()`]: #method.request_verification + pub async fn start_verification(&self) -> Result { let (sas, request) = self.inner.start_verification().await?; self.client.send_to_device(&request).await?; - Ok(Sas { inner: sas, client: self.client.clone() }) + Ok(SasVerification { inner: sas, client: self.client.clone() }) } - /// Is the device trusted. - pub fn is_trusted(&self) -> bool { - self.inner.trust_state() + /// Request an interacitve verification with this `Device` + /// + /// Returns a `VerificationRequest` object and a to-device request that + /// needs to be sent out. + /// + /// The default methods that are supported are `m.sas.v1` and + /// `m.qr_code.show.v1`, if this isn't desireable the + /// [`request_verification_with_methods()`] method can be used to override + /// this. + /// + /// # Examples + /// + /// ```no_run + /// # use std::convert::TryFrom; + /// # use matrix_sdk::{Client, ruma::UserId}; + /// # use url::Url; + /// # use futures::executor::block_on; + /// # let alice = UserId::try_from("@alice:example.org").unwrap(); + /// # let homeserver = Url::parse("http://example.com").unwrap(); + /// # let client = Client::new(homeserver).unwrap(); + /// # block_on(async { + /// let device = client.get_device(&alice, "DEVICEID".into()) + /// .await + /// .unwrap() + /// .unwrap(); + /// + /// let verification = device.request_verification().await.unwrap(); + /// # }); + /// ``` + /// + /// [`request_verification_with_methods()`]: + /// #method.request_verification_with_methods + pub async fn request_verification(&self) -> Result { + let (verification, request) = self.inner.request_verification().await; + self.client.send_verification_request(request).await?; + + Ok(VerificationRequest { inner: verification, client: self.client.clone() }) + } + + /// Request an interacitve verification with this `Device` + /// + /// Returns a `VerificationRequest` object and a to-device request that + /// needs to be sent out. + /// + /// # Arguments + /// + /// * `methods` - The verification methods that we want to support. + /// + /// # Examples + /// + /// ```no_run + /// # use std::convert::TryFrom; + /// # use matrix_sdk::{ + /// # Client, + /// # ruma::{ + /// # UserId, + /// # events::key::verification::VerificationMethod, + /// # } + /// # }; + /// # use url::Url; + /// # use futures::executor::block_on; + /// # let alice = UserId::try_from("@alice:example.org").unwrap(); + /// # let homeserver = Url::parse("http://example.com").unwrap(); + /// # let client = Client::new(homeserver).unwrap(); + /// # block_on(async { + /// let device = client.get_device(&alice, "DEVICEID".into()) + /// .await + /// .unwrap() + /// .unwrap(); + /// + /// // We don't want to support showing a QR code, we only support SAS + /// // verification + /// let methods = vec![VerificationMethod::SasV1]; + /// + /// let verification = device.request_verification_with_methods(methods).await.unwrap(); + /// # }); + /// ``` + pub async fn request_verification_with_methods( + &self, + methods: Vec, + ) -> Result { + let (verification, request) = self.inner.request_verification_with_methods(methods).await; + self.client.send_verification_request(request).await?; + + Ok(VerificationRequest { inner: verification, client: self.client.clone() }) + } + + /// Is the device considered to be verified, either by locally trusting it + /// or using cross signing. + pub fn verified(&self) -> bool { + self.inner.verified() } /// Set the local trust state of the device to the given state. diff --git a/matrix_sdk/src/error.rs b/matrix_sdk/src/error.rs index ca9c77d8..6ebb9716 100644 --- a/matrix_sdk/src/error.rs +++ b/matrix_sdk/src/error.rs @@ -18,8 +18,10 @@ use std::io::Error as IoError; use http::StatusCode; #[cfg(feature = "encryption")] -use matrix_sdk_base::crypto::{store::CryptoStoreError, DecryptorError}; -use matrix_sdk_base::{Error as MatrixError, StoreError}; +use matrix_sdk_base::crypto::{ + CryptoStoreError, DecryptorError, KeyExportError, MegolmError, OlmError, +}; +use matrix_sdk_base::{Error as SdkBaseError, StoreError}; use reqwest::Error as ReqwestError; use ruma::{ api::{ @@ -114,17 +116,27 @@ pub enum Error { #[error(transparent)] Io(#[from] IoError), - /// An error occurred in the Matrix client library. - #[error(transparent)] - MatrixError(#[from] MatrixError), - /// An error occurred in the crypto store. #[cfg(feature = "encryption")] + #[cfg_attr(feature = "docs", doc(cfg(encryption)))] #[error(transparent)] CryptoStoreError(#[from] CryptoStoreError), + /// An error occurred during a E2EE operation. + #[cfg(feature = "encryption")] + #[cfg_attr(feature = "docs", doc(cfg(encryption)))] + #[error(transparent)] + OlmError(#[from] OlmError), + + /// An error occurred during a E2EE group operation. + #[cfg(feature = "encryption")] + #[cfg_attr(feature = "docs", doc(cfg(encryption)))] + #[error(transparent)] + MegolmError(#[from] MegolmError), + /// An error occurred during decryption. #[cfg(feature = "encryption")] + #[cfg_attr(feature = "docs", doc(cfg(encryption)))] #[error(transparent)] DecryptorError(#[from] DecryptorError), @@ -141,6 +153,35 @@ pub enum Error { Url(#[from] UrlParseError), } +/// Error for the room key importing functionality. +#[cfg(feature = "encryption")] +#[cfg_attr(feature = "docs", doc(cfg(encryption)))] +#[derive(Error, Debug)] +// This is allowed because key importing isn't enabled under wasm. +#[allow(dead_code)] +pub enum RoomKeyImportError { + /// An error de/serializing type for the `StateStore` + #[error(transparent)] + SerdeJson(#[from] JsonError), + + /// The cryptostore isn't yet open, logging in is required to open the + /// cryptostore. + #[error("The cryptostore hasn't been yet opened, can't import yet.")] + StoreClosed, + + /// An IO error happened. + #[error(transparent)] + Io(#[from] IoError), + + /// An error occurred in the crypto store. + #[error(transparent)] + CryptoStore(#[from] CryptoStoreError), + + /// An error occurred while importing the key export. + #[error(transparent)] + Export(#[from] KeyExportError), +} + impl Error { /// Try to destructure the error into an universal interactive auth info. /// @@ -165,6 +206,23 @@ impl Error { } } +impl From for Error { + fn from(e: SdkBaseError) -> Self { + match e { + SdkBaseError::AuthenticationRequired => Self::AuthenticationRequired, + SdkBaseError::StateStore(e) => Self::StateStore(e), + SdkBaseError::SerdeJson(e) => Self::SerdeJson(e), + SdkBaseError::IoError(e) => Self::Io(e), + #[cfg(feature = "encryption")] + SdkBaseError::CryptoStore(e) => Self::CryptoStoreError(e), + #[cfg(feature = "encryption")] + SdkBaseError::OlmError(e) => Self::OlmError(e), + #[cfg(feature = "encryption")] + SdkBaseError::MegolmError(e) => Self::MegolmError(e), + } + } +} + impl From for Error { fn from(e: ReqwestError) -> Self { Error::Http(HttpError::Reqwest(e)) diff --git a/matrix_sdk/src/event_handler/mod.rs b/matrix_sdk/src/event_handler/mod.rs index 391acc79..e7cf2105 100644 --- a/matrix_sdk/src/event_handler/mod.rs +++ b/matrix_sdk/src/event_handler/mod.rs @@ -14,6 +14,7 @@ // limitations under the License. use std::ops::Deref; +use matrix_sdk_base::{hoist_and_deserialize_state_event, hoist_room_event_prev_content}; use matrix_sdk_common::async_trait; use ruma::{ api::client::r0::push::get_notifications::Notification, @@ -27,6 +28,7 @@ use ruma::{ ignored_user_list::IgnoredUserListEventContent, presence::PresenceEvent, push_rules::PushRulesEventContent, + reaction::ReactionEventContent, receipt::ReceiptEventContent, room::{ aliases::AliasesEventContent, @@ -46,6 +48,7 @@ use ruma::{ GlobalAccountDataEvent, RoomAccountDataEvent, StrippedStateEvent, SyncEphemeralRoomEvent, SyncMessageEvent, SyncStateEvent, }, + serde::Raw, RoomId, }; use serde_json::value::RawValue as RawJsonValue; @@ -88,14 +91,24 @@ impl Handler { self.handle_room_account_data_event(room.clone(), &event).await; } - for event in room_info.state.events.iter().filter_map(|e| e.deserialize().ok()) { - self.handle_state_event(room.clone(), &event).await; + for (raw_event, event) in room_info.state.events.iter().filter_map(|e| { + if let Ok(d) = hoist_and_deserialize_state_event(e) { + Some((e, d)) + } else { + None + } + }) { + self.handle_state_event(room.clone(), &event, raw_event).await; } - for event in - room_info.timeline.events.iter().filter_map(|e| e.event.deserialize().ok()) - { - self.handle_timeline_event(room.clone(), &event).await; + for (raw_event, event) in room_info.timeline.events.iter().filter_map(|e| { + if let Ok(d) = hoist_room_event_prev_content(&e.event) { + Some((&e.event, d)) + } else { + None + } + }) { + self.handle_timeline_event(room.clone(), &event, raw_event).await; } } } @@ -108,14 +121,24 @@ impl Handler { self.handle_room_account_data_event(room.clone(), &event).await; } - for event in room_info.state.events.iter().filter_map(|e| e.deserialize().ok()) { - self.handle_state_event(room.clone(), &event).await; + for (raw_event, event) in room_info.state.events.iter().filter_map(|e| { + if let Ok(d) = hoist_and_deserialize_state_event(e) { + Some((e, d)) + } else { + None + } + }) { + self.handle_state_event(room.clone(), &event, raw_event).await; } - for event in - room_info.timeline.events.iter().filter_map(|e| e.event.deserialize().ok()) - { - self.handle_timeline_event(room.clone(), &event).await; + for (raw_event, event) in room_info.timeline.events.iter().filter_map(|e| { + if let Ok(d) = hoist_room_event_prev_content(&e.event) { + Some((&e.event, d)) + } else { + None + } + }) { + self.handle_timeline_event(room.clone(), &event, raw_event).await; } } } @@ -143,7 +166,12 @@ impl Handler { } } - async fn handle_timeline_event(&self, room: Room, event: &AnySyncRoomEvent) { + async fn handle_timeline_event( + &self, + room: Room, + event: &AnySyncRoomEvent, + raw_event: &Raw, + ) { match event { AnySyncRoomEvent::State(event) => match event { AnySyncStateEvent::RoomMember(e) => self.on_room_member(room, e).await, @@ -156,10 +184,25 @@ impl Handler { AnySyncStateEvent::RoomPowerLevels(e) => self.on_room_power_levels(room, e).await, AnySyncStateEvent::RoomTombstone(e) => self.on_room_tombstone(room, e).await, AnySyncStateEvent::RoomJoinRules(e) => self.on_room_join_rules(room, e).await, - AnySyncStateEvent::Custom(e) => { - self.on_custom_event(room, &CustomEvent::State(e)).await + AnySyncStateEvent::PolicyRuleRoom(_) + | AnySyncStateEvent::PolicyRuleServer(_) + | AnySyncStateEvent::PolicyRuleUser(_) + | AnySyncStateEvent::RoomCreate(_) + | AnySyncStateEvent::RoomEncryption(_) + | AnySyncStateEvent::RoomGuestAccess(_) + | AnySyncStateEvent::RoomHistoryVisibility(_) + | AnySyncStateEvent::RoomPinnedEvents(_) + | AnySyncStateEvent::RoomServerAcl(_) + | AnySyncStateEvent::RoomThirdPartyInvite(_) + | AnySyncStateEvent::RoomTopic(_) + | AnySyncStateEvent::SpaceChild(_) + | AnySyncStateEvent::SpaceParent(_) => {} + _ => { + if let Ok(e) = raw_event.deserialize_as::>() + { + self.on_custom_event(room, &CustomEvent::State(&e)).await; + } } - _ => {} }, AnySyncRoomEvent::Message(event) => match event { AnySyncMessageEvent::RoomMessage(e) => self.on_room_message(room, e).await, @@ -167,23 +210,41 @@ impl Handler { self.on_room_message_feedback(room, e).await } AnySyncMessageEvent::RoomRedaction(e) => self.on_room_redaction(room, e).await, - AnySyncMessageEvent::Custom(e) => { - self.on_custom_event(room, &CustomEvent::Message(e)).await - } + AnySyncMessageEvent::Reaction(e) => self.on_room_reaction(room, e).await, AnySyncMessageEvent::CallInvite(e) => self.on_room_call_invite(room, e).await, AnySyncMessageEvent::CallAnswer(e) => self.on_room_call_answer(room, e).await, AnySyncMessageEvent::CallCandidates(e) => { self.on_room_call_candidates(room, e).await } AnySyncMessageEvent::CallHangup(e) => self.on_room_call_hangup(room, e).await, - _ => {} + AnySyncMessageEvent::KeyVerificationReady(_) + | AnySyncMessageEvent::KeyVerificationStart(_) + | AnySyncMessageEvent::KeyVerificationCancel(_) + | AnySyncMessageEvent::KeyVerificationAccept(_) + | AnySyncMessageEvent::KeyVerificationKey(_) + | AnySyncMessageEvent::KeyVerificationMac(_) + | AnySyncMessageEvent::KeyVerificationDone(_) + | AnySyncMessageEvent::RoomEncrypted(_) + | AnySyncMessageEvent::Sticker(_) => {} + _ => { + if let Ok(e) = + raw_event.deserialize_as::>() + { + self.on_custom_event(room, &CustomEvent::Message(&e)).await; + } + } }, AnySyncRoomEvent::RedactedState(_event) => {} AnySyncRoomEvent::RedactedMessage(_event) => {} } } - async fn handle_state_event(&self, room: Room, event: &AnySyncStateEvent) { + async fn handle_state_event( + &self, + room: Room, + event: &AnySyncStateEvent, + raw_event: &Raw, + ) { match event { AnySyncStateEvent::RoomMember(member) => self.on_state_member(room, member).await, AnySyncStateEvent::RoomName(name) => self.on_state_name(room, name).await, @@ -200,10 +261,24 @@ impl Handler { // TODO make `on_state_tombstone` method self.on_room_tombstone(room, tomb).await } - AnySyncStateEvent::Custom(custom) => { - self.on_custom_event(room, &CustomEvent::State(custom)).await + AnySyncStateEvent::PolicyRuleRoom(_) + | AnySyncStateEvent::PolicyRuleServer(_) + | AnySyncStateEvent::PolicyRuleUser(_) + | AnySyncStateEvent::RoomCreate(_) + | AnySyncStateEvent::RoomEncryption(_) + | AnySyncStateEvent::RoomGuestAccess(_) + | AnySyncStateEvent::RoomHistoryVisibility(_) + | AnySyncStateEvent::RoomPinnedEvents(_) + | AnySyncStateEvent::RoomServerAcl(_) + | AnySyncStateEvent::RoomThirdPartyInvite(_) + | AnySyncStateEvent::RoomTopic(_) + | AnySyncStateEvent::SpaceChild(_) + | AnySyncStateEvent::SpaceParent(_) => {} + _ => { + if let Ok(e) = raw_event.deserialize_as::>() { + self.on_custom_event(room, &CustomEvent::State(&e)).await; + } } - _ => {} } } @@ -301,7 +376,7 @@ pub enum CustomEvent<'c> { /// # use matrix_sdk::{ /// # async_trait, /// # EventHandler, -/// # events::{ +/// # ruma::events::{ /// # room::message::{MessageEventContent, MessageType, TextMessageEventContent}, /// # SyncMessageEvent /// # }, @@ -358,6 +433,8 @@ pub trait EventHandler: Send + Sync { async fn on_room_message(&self, _: Room, _: &SyncMessageEvent) {} /// Fires when `Client` receives a `RoomEvent::RoomMessageFeedback` event. async fn on_room_message_feedback(&self, _: Room, _: &SyncMessageEvent) {} + /// Fires when `Client` receives a `RoomEvent::Reaction` event. + async fn on_room_reaction(&self, _: Room, _: &SyncMessageEvent) {} /// Fires when `Client` receives a `RoomEvent::CallInvite` event async fn on_room_call_invite(&self, _: Room, _: &SyncMessageEvent) {} /// Fires when `Client` receives a `RoomEvent::CallAnswer` event diff --git a/matrix_sdk/src/http_client.rs b/matrix_sdk/src/http_client.rs index 1899b5af..ed5477a4 100644 --- a/matrix_sdk/src/http_client.rs +++ b/matrix_sdk/src/http_client.rs @@ -12,15 +12,10 @@ // See the License for the specific language governing permissions and // limitations under the License. -#[cfg(all(not(target_arch = "wasm32")))] -use std::sync::atomic::{AtomicU64, Ordering}; use std::{convert::TryFrom, fmt::Debug, sync::Arc}; -#[cfg(all(not(target_arch = "wasm32")))] -use backoff::{future::retry, Error as RetryError, ExponentialBackoff}; -#[cfg(all(not(target_arch = "wasm32")))] -use http::StatusCode; -use http::{HeaderValue, Response as HttpResponse}; +use bytes::{Bytes, BytesMut}; +use http::Response as HttpResponse; use matrix_sdk_common::{async_trait, locks::RwLock, AsyncTraitDeps}; use reqwest::{Client, Response}; use ruma::api::{ @@ -30,7 +25,7 @@ use ruma::api::{ use tracing::trace; use url::Url; -use crate::{error::HttpError, Bytes, BytesMut, ClientConfig, RequestConfig, Session}; +use crate::{error::HttpError, ClientConfig, RequestConfig, Session}; /// Abstraction around the http layer. The allows implementors to use different /// http libraries. @@ -54,7 +49,7 @@ pub trait HttpSend: AsyncTraitDeps { /// /// ``` /// use std::convert::TryFrom; - /// use matrix_sdk::{HttpSend, async_trait, HttpError, RequestConfig, Bytes}; + /// use matrix_sdk::{HttpSend, async_trait, HttpError, RequestConfig, bytes::Bytes}; /// /// #[derive(Debug)] /// struct Client(reqwest::Client); @@ -235,6 +230,8 @@ pub(crate) fn client_with_config(config: &ClientConfig) -> Result, config: RequestConfig, ) -> Result, HttpError> { + use std::sync::atomic::{AtomicU64, Ordering}; + + use backoff::{future::retry, Error as RetryError, ExponentialBackoff}; + use http::StatusCode; + let mut backoff = ExponentialBackoff::default(); let mut request = reqwest::Request::try_from(request)?; let retry_limit = config.retry_limit; diff --git a/matrix_sdk/src/lib.rs b/matrix_sdk/src/lib.rs index 4473662e..be0c099e 100644 --- a/matrix_sdk/src/lib.rs +++ b/matrix_sdk/src/lib.rs @@ -75,33 +75,18 @@ compile_error!("only one of 'native-tls' or 'rustls-tls' features can be enabled #[cfg(all(feature = "sso_login", target_arch = "wasm32"))] compile_error!("'sso_login' cannot be enabled on 'wasm32' arch"); -pub use bytes::{Bytes, BytesMut}; +pub use bytes; #[cfg(feature = "encryption")] #[cfg_attr(feature = "docs", doc(cfg(encryption)))] pub use matrix_sdk_base::crypto::{EncryptionInfo, LocalTrust}; pub use matrix_sdk_base::{ - media, Error as BaseError, Room as BaseRoom, RoomInfo, RoomMember as BaseRoomMember, RoomType, - Session, StateChanges, StoreError, + media, Room as BaseRoom, RoomInfo, RoomMember as BaseRoomMember, RoomType, Session, + StateChanges, StoreError, }; pub use matrix_sdk_common::*; pub use reqwest; -#[cfg(feature = "appservice")] -pub use ruma::{ - api::{appservice as api_appservice, IncomingRequest, OutgoingRequestAppserviceExt}, - serde::{exports::serde::de::value::Error as SerdeError, urlencoded}, -}; -pub use ruma::{ - api::{ - client as api, - error::{ - FromHttpRequestError, FromHttpResponseError, IntoHttpError, MatrixError, ServerError, - }, - AuthScheme, EndpointError, IncomingResponse, OutgoingRequest, SendAccessToken, - }, - assign, directory, encryption, events, identifiers, int, presence, push, receipt, - serde::{CanonicalJsonValue, Raw}, - thirdparty, uint, Int, MilliSecondsSinceUnixEpoch, Outgoing, SecondsSinceUnixEpoch, UInt, -}; +#[doc(no_inline)] +pub use ruma; mod client; mod error; @@ -115,9 +100,7 @@ mod room_member; #[cfg(feature = "encryption")] mod device; #[cfg(feature = "encryption")] -mod sas; -#[cfg(feature = "encryption")] -mod verification_request; +pub mod verification; pub use client::{Client, ClientConfig, LoopCtrl, RequestConfig, SyncSettings}; #[cfg(feature = "encryption")] @@ -127,12 +110,5 @@ pub use error::{Error, HttpError, Result}; pub use event_handler::{CustomEvent, EventHandler}; pub use http_client::HttpSend; pub use room_member::RoomMember; -#[cfg(feature = "encryption")] -#[cfg_attr(feature = "docs", doc(cfg(encryption)))] -pub use sas::Sas; -#[cfg(feature = "encryption")] -#[cfg_attr(feature = "docs", doc(cfg(encryption)))] -pub use verification_request::VerificationRequest; - #[cfg(not(target_arch = "wasm32"))] pub(crate) const VERSION: &str = env!("CARGO_PKG_VERSION"); diff --git a/matrix_sdk/src/room/common.rs b/matrix_sdk/src/room/common.rs index 2e4aeebf..cfb9f31b 100644 --- a/matrix_sdk/src/room/common.rs +++ b/matrix_sdk/src/room/common.rs @@ -4,16 +4,19 @@ use matrix_sdk_base::deserialized_responses::MembersResponse; use matrix_sdk_common::locks::Mutex; use ruma::{ api::client::r0::{ - media::{get_content, get_content_thumbnail}, membership::{get_member_events, join_room_by_id, leave_room}, message::get_message_events, }, - events::{AnySyncStateEvent, EventType}, + events::{room::history_visibility::HistoryVisibility, AnySyncStateEvent, EventType}, serde::Raw, UserId, }; -use crate::{BaseRoom, Client, Result, RoomMember}; +use crate::{ + media::{MediaFormat, MediaRequest, MediaType}, + room::RoomType, + BaseRoom, Client, Result, RoomMember, +}; /// A struct containing methods that are common for Joined, Invited and Left /// Rooms @@ -65,20 +68,20 @@ impl Common { /// Gets the avatar of this room, if set. /// - /// Returns the avatar. No guarantee on the size of the image is given. - /// If no size is given the full-sized avatar will be returned. + /// Returns the avatar. + /// If a thumbnail is requested no guarantee on the size of the image is + /// given. /// /// # Arguments /// - /// * `width` - The desired width of the avatar. - /// - /// * `height` - The desired height of the avatar. + /// * `format` - The desired format of the avatar. /// /// # Example /// ```no_run /// # use futures::executor::block_on; /// # use matrix_sdk::Client; - /// # use matrix_sdk::identifiers::room_id; + /// # use matrix_sdk::ruma::room_id; + /// # use matrix_sdk::media::MediaFormat; /// # use url::Url; /// # let homeserver = Url::parse("http://example.com").unwrap(); /// # block_on(async { @@ -89,24 +92,15 @@ impl Common { /// let room = client /// .get_joined_room(&room_id) /// .unwrap(); - /// if let Some(avatar) = room.avatar(Some(96), Some(96)).await.unwrap() { + /// if let Some(avatar) = room.avatar(MediaFormat::File).await.unwrap() { /// std::fs::write("avatar.png", avatar); /// } /// # }) /// ``` - pub async fn avatar(&self, width: Option, height: Option) -> Result>> { - // TODO: try to offer the avatar from cache, requires avatar cache + pub async fn avatar(&self, format: MediaFormat) -> Result>> { if let Some(url) = self.avatar_url() { - if let (Some(width), Some(height)) = (width, height) { - let request = - get_content_thumbnail::Request::from_url(&url, width.into(), height.into())?; - let response = self.client.send(request, None).await?; - Ok(Some(response.file)) - } else { - let request = get_content::Request::from_url(&url)?; - let response = self.client.send(request, None).await?; - Ok(Some(response.file)) - } + let request = MediaRequest { media_type: MediaType::Uri(url.clone()), format }; + Ok(Some(self.client.get_media_content(&request, true).await?)) } else { Ok(None) } @@ -125,9 +119,11 @@ impl Common { /// ```no_run /// # use std::convert::TryFrom; /// use matrix_sdk::Client; - /// # use matrix_sdk::identifiers::room_id; - /// # use matrix_sdk::api::r0::filter::RoomEventFilter; - /// # use matrix_sdk::api::r0::message::get_message_events::Request as MessagesRequest; + /// # use matrix_sdk::ruma::room_id; + /// # use matrix_sdk::ruma::api::client::r0::{ + /// # filter::RoomEventFilter, + /// # message::get_message_events::Request as MessagesRequest, + /// # }; /// # use url::Url; /// /// # let homeserver = Url::parse("http://example.com").unwrap(); @@ -178,6 +174,10 @@ impl Common { } async fn ensure_members(&self) -> Result<()> { + if !self.are_events_visible() { + return Ok(()); + } + if !self.are_members_synced() { self.request_members().await?; } @@ -185,6 +185,17 @@ impl Common { Ok(()) } + fn are_events_visible(&self) -> bool { + if let RoomType::Invited = self.inner.room_type() { + return matches!( + self.inner.history_visibility(), + HistoryVisibility::WorldReadable | HistoryVisibility::Invited + ); + } + + true + } + /// Sync the member list with the server. /// /// This method will de-duplicate requests if it is called multiple times in diff --git a/matrix_sdk/src/room/joined.rs b/matrix_sdk/src/room/joined.rs index 3010dd5f..2d5be644 100644 --- a/matrix_sdk/src/room/joined.rs +++ b/matrix_sdk/src/room/joined.rs @@ -38,8 +38,8 @@ use ruma::{ }, AnyMessageEventContent, AnyStateEventContent, }, - identifiers::{EventId, UserId}, receipt::ReceiptType, + EventId, UserId, }; #[cfg(feature = "encryption")] use tracing::instrument; @@ -159,10 +159,10 @@ impl Joined { /// /// ```no_run /// use std::time::Duration; - /// use matrix_sdk::api::r0::typing::create_typing_event::Typing; + /// use matrix_sdk::ruma::api::client::r0::typing::create_typing_event::Typing; /// # use matrix_sdk::{ /// # Client, SyncSettings, - /// # identifiers::room_id, + /// # ruma::room_id, /// # }; /// # use futures::executor::block_on; /// # use url::Url; @@ -349,9 +349,9 @@ impl Joined { /// # use matrix_sdk::{Client, SyncSettings}; /// # use url::Url; /// # use futures::executor::block_on; - /// # use matrix_sdk::identifiers::room_id; + /// # use matrix_sdk::ruma::room_id; /// # use std::convert::TryFrom; - /// use matrix_sdk::events::{ + /// use matrix_sdk::ruma::events::{ /// AnyMessageEventContent, /// room::message::{MessageEventContent, TextMessageEventContent}, /// }; @@ -431,7 +431,7 @@ impl Joined { /// /// ```no_run /// # use std::{path::PathBuf, fs::File, io::Read}; - /// # use matrix_sdk::{Client, identifiers::room_id}; + /// # use matrix_sdk::{Client, ruma::room_id}; /// # use url::Url; /// # use mime; /// # use futures::executor::block_on; @@ -532,18 +532,17 @@ impl Joined { /// # Example /// /// ```no_run - /// use matrix_sdk::{ + /// use matrix_sdk::ruma::{ /// events::{ /// AnyStateEventContent, /// room::member::{MemberEventContent, MembershipState}, /// }, - /// identifiers::mxc_uri, - /// assign, + /// assign, mxc_uri, /// }; /// # futures::executor::block_on(async { /// # let homeserver = url::Url::parse("http://localhost:8080").unwrap(); /// # let mut client = matrix_sdk::Client::new(homeserver).unwrap(); - /// # let room_id = matrix_sdk::identifiers::room_id!("!test:localhost"); + /// # let room_id = matrix_sdk::ruma::room_id!("!test:localhost"); /// /// let avatar_url = mxc_uri!("mxc://example.org/avatar"); /// let member_event = assign!(MemberEventContent::new(MembershipState::Join), { @@ -591,11 +590,11 @@ impl Joined { /// # futures::executor::block_on(async { /// # let homeserver = url::Url::parse("http://localhost:8080").unwrap(); /// # let mut client = matrix_sdk::Client::new(homeserver).unwrap(); - /// # let room_id = matrix_sdk::identifiers::room_id!("!test:localhost"); + /// # let room_id = matrix_sdk::ruma::room_id!("!test:localhost"); /// # let room = client /// # .get_joined_room(&room_id) /// # .unwrap(); - /// let event_id = matrix_sdk::identifiers::event_id!("$xxxxxx:example.org"); + /// let event_id = matrix_sdk::ruma::event_id!("$xxxxxx:example.org"); /// let reason = Some("Indecent material"); /// room.redact(&event_id, reason, None).await.unwrap(); /// # }) diff --git a/matrix_sdk/src/room_member.rs b/matrix_sdk/src/room_member.rs index e9d06382..488ffee8 100644 --- a/matrix_sdk/src/room_member.rs +++ b/matrix_sdk/src/room_member.rs @@ -1,8 +1,9 @@ use std::ops::Deref; -use ruma::api::client::r0::media::{get_content, get_content_thumbnail}; - -use crate::{BaseRoomMember, Client, Result}; +use crate::{ + media::{MediaFormat, MediaRequest, MediaType}, + BaseRoomMember, Client, Result, +}; /// The high-level `RoomMember` representation #[derive(Debug, Clone)] @@ -26,21 +27,21 @@ impl RoomMember { /// Gets the avatar of this member, if set. /// - /// Returns the avatar. No guarantee on the size of the image is given. - /// If no size is given the full-sized avatar will be returned. + /// Returns the avatar. + /// If a thumbnail is requested no guarantee on the size of the image is + /// given. /// /// # Arguments /// - /// * `width` - The desired width of the avatar. - /// - /// * `height` - The desired height of the avatar. + /// * `format` - The desired format of the avatar. /// /// # Example /// ```no_run /// # use futures::executor::block_on; /// # use matrix_sdk::Client; - /// # use matrix_sdk::identifiers::room_id; + /// # use matrix_sdk::ruma::room_id; /// # use matrix_sdk::RoomMember; + /// # use matrix_sdk::media::MediaFormat; /// # use url::Url; /// # let homeserver = Url::parse("http://example.com").unwrap(); /// # block_on(async { @@ -53,24 +54,15 @@ impl RoomMember { /// .unwrap(); /// let members = room.members().await.unwrap(); /// let member = members.first().unwrap(); - /// if let Some(avatar) = member.avatar(Some(96), Some(96)).await.unwrap() { + /// if let Some(avatar) = member.avatar(MediaFormat::File).await.unwrap() { /// std::fs::write("avatar.png", avatar); /// } /// # }) /// ``` - pub async fn avatar(&self, width: Option, height: Option) -> Result>> { - // TODO: try to offer the avatar from cache, requires avatar cache + pub async fn avatar(&self, format: MediaFormat) -> Result>> { if let Some(url) = self.avatar_url() { - if let (Some(width), Some(height)) = (width, height) { - let request = - get_content_thumbnail::Request::from_url(url, width.into(), height.into())?; - let response = self.client.send(request, None).await?; - Ok(Some(response.file)) - } else { - let request = get_content::Request::from_url(url)?; - let response = self.client.send(request, None).await?; - Ok(Some(response.file)) - } + let request = MediaRequest { media_type: MediaType::Uri(url.clone()), format }; + Ok(Some(self.client.get_media_content(&request, true).await?)) } else { Ok(None) } diff --git a/matrix_sdk/src/verification/mod.rs b/matrix_sdk/src/verification/mod.rs new file mode 100644 index 00000000..80a21f2b --- /dev/null +++ b/matrix_sdk/src/verification/mod.rs @@ -0,0 +1,138 @@ +// Copyright 2021 The Matrix.org Foundation C.I.C. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Interactive verification for E2EE capable users and devices in Matrix. +//! +//! The SDK supports interactive verification of devices and users, this module +//! contains types that model and support different verification flows. +//! +//! A verification flow usually starts its life as a [VerificationRequest], the +//! request can then be accepted, or it needs to be accepted by the other side +//! of the verification flow. +//! +//! Once both sides have agreed to pereform the verification, and the +//! [VerificationRequest::is_ready()] method returns true, the verification can +//! transition into one of the supported verification flows: +//! +//! * [SasVerification] - Interactive verification using a short authentication +//! string. +//! * [QrVerification] - Interactive verification using QR codes. + +mod qrcode; +mod requests; +mod sas; + +pub use matrix_sdk_base::crypto::{AcceptSettings, CancelInfo}; +pub use qrcode::QrVerification; +pub use requests::VerificationRequest; +pub use sas::SasVerification; + +/// An enum over the different verification types the SDK supports. +#[derive(Debug, Clone)] +pub enum Verification { + /// The `m.sas.v1` verification variant. + SasV1(SasVerification), + /// The `m.qr_code.*.v1` verification variant. + QrV1(QrVerification), +} + +impl Verification { + /// Try to deconstruct this verification enum into a SAS verification. + pub fn sas(self) -> Option { + if let Verification::SasV1(sas) = self { + Some(sas) + } else { + None + } + } + + /// Try to deconstruct this verification enum into a QR code verification. + pub fn qr(self) -> Option { + if let Verification::QrV1(qr) = self { + Some(qr) + } else { + None + } + } + + /// Has this verification finished. + pub fn is_done(&self) -> bool { + match self { + Verification::SasV1(s) => s.is_done(), + Verification::QrV1(qr) => qr.is_done(), + } + } + + /// Has the verification been cancelled. + pub fn is_cancelled(&self) -> bool { + match self { + Verification::SasV1(s) => s.is_cancelled(), + Verification::QrV1(qr) => qr.is_cancelled(), + } + } + + /// Get info about the cancellation if the verification flow has been + /// cancelled. + pub fn cancel_info(&self) -> Option { + match self { + Verification::SasV1(s) => s.cancel_info(), + Verification::QrV1(q) => q.cancel_info(), + } + } + + /// Get our own user id. + pub fn own_user_id(&self) -> &ruma::UserId { + match self { + Verification::SasV1(v) => v.own_user_id(), + Verification::QrV1(v) => v.own_user_id(), + } + } + + /// Get the user id of the other user participating in this verification + /// flow. + pub fn other_user_id(&self) -> &ruma::UserId { + match self { + Verification::SasV1(v) => v.inner.other_user_id(), + Verification::QrV1(v) => v.inner.other_user_id(), + } + } + + /// Is this a verification that is veryfying one of our own devices. + pub fn is_self_verification(&self) -> bool { + match self { + Verification::SasV1(v) => v.is_self_verification(), + Verification::QrV1(v) => v.is_self_verification(), + } + } + + /// Did we initiate the verification flow. + pub fn we_started(&self) -> bool { + match self { + Verification::SasV1(s) => s.we_started(), + Verification::QrV1(q) => q.we_started(), + } + } +} + +impl From for Verification { + fn from(sas: SasVerification) -> Self { + Self::SasV1(sas) + } +} + +impl From for Verification { + fn from(qr: QrVerification) -> Self { + Self::QrV1(qr) + } +} diff --git a/matrix_sdk/src/verification/qrcode.rs b/matrix_sdk/src/verification/qrcode.rs new file mode 100644 index 00000000..78b80e80 --- /dev/null +++ b/matrix_sdk/src/verification/qrcode.rs @@ -0,0 +1,104 @@ +// Copyright 2021 The Matrix.org Foundation C.I.C. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use matrix_sdk_base::crypto::{ + matrix_qrcode::{qrcode::QrCode, EncodingError}, + CancelInfo, QrVerification as BaseQrVerification, +}; +use ruma::UserId; + +use crate::{Client, Result}; + +/// An object controlling QR code style key verification flows. +#[derive(Debug, Clone)] +pub struct QrVerification { + pub(crate) inner: BaseQrVerification, + pub(crate) client: Client, +} + +impl QrVerification { + /// Get our own user id. + pub fn own_user_id(&self) -> &UserId { + self.inner.user_id() + } + + /// Is this a verification that is veryfying one of our own devices. + pub fn is_self_verification(&self) -> bool { + self.inner.is_self_verification() + } + + /// Has this verification finished. + pub fn is_done(&self) -> bool { + self.inner.is_done() + } + + /// Did we initiate the verification flow. + pub fn we_started(&self) -> bool { + self.inner.we_started() + } + + /// Get info about the cancellation if the verification flow has been + /// cancelled. + pub fn cancel_info(&self) -> Option { + self.inner.cancel_info() + } + + /// Get the user id of the other user participating in this verification + /// flow. + pub fn other_user_id(&self) -> &UserId { + self.inner.other_user_id() + } + + /// Has the verification been cancelled. + pub fn is_cancelled(&self) -> bool { + self.inner.is_cancelled() + } + + /// Generate a QR code object that is representing this verification flow. + /// + /// The `QrCode` can then be rendered as an image or as an unicode string. + /// + /// The [`to_bytes()`](#method.to_bytes) method can be used to instead + /// output the raw bytes that should be encoded as a QR code. + pub fn to_qr_code(&self) -> std::result::Result { + self.inner.to_qr_code() + } + + /// Generate a the raw bytes that should be encoded as a QR code is + /// representing this verification flow. + /// + /// The [`to_qr_code()`](#method.to_qr_code) method can be used to instead + /// output a `QrCode` object that can be rendered. + pub fn to_bytes(&self) -> std::result::Result, EncodingError> { + self.inner.to_bytes() + } + + /// Confirm that the other side has scanned our QR code. + pub async fn confirm(&self) -> Result<()> { + if let Some(request) = self.inner.confirm_scanning() { + self.client.send_verification_request(request).await?; + } + + Ok(()) + } + + /// Abort the verification flow and notify the other side that we did so. + pub async fn cancel(&self) -> Result<()> { + if let Some(request) = self.inner.cancel() { + self.client.send_verification_request(request).await?; + } + + Ok(()) + } +} diff --git a/matrix_sdk/src/verification/requests.rs b/matrix_sdk/src/verification/requests.rs new file mode 100644 index 00000000..b7b39485 --- /dev/null +++ b/matrix_sdk/src/verification/requests.rs @@ -0,0 +1,143 @@ +// Copyright 2021 The Matrix.org Foundation C.I.C. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use matrix_sdk_base::crypto::{CancelInfo, VerificationRequest as BaseVerificationRequest}; +use ruma::events::key::verification::VerificationMethod; + +use super::{QrVerification, SasVerification}; +use crate::{Client, Result}; + +/// An object controlling the interactive verification flow. +#[derive(Debug, Clone)] +pub struct VerificationRequest { + pub(crate) inner: BaseVerificationRequest, + pub(crate) client: Client, +} + +impl VerificationRequest { + /// Has this verification finished. + pub fn is_done(&self) -> bool { + self.inner.is_done() + } + + /// Has the verification been cancelled. + pub fn is_cancelled(&self) -> bool { + self.inner.is_cancelled() + } + + /// Get info about the cancellation if the verification request has been + /// cancelled. + pub fn cancel_info(&self) -> Option { + self.inner.cancel_info() + } + + /// Get our own user id. + pub fn own_user_id(&self) -> &ruma::UserId { + self.inner.own_user_id() + } + + /// Has the verification request been answered by another device. + pub fn is_passive(&self) -> bool { + self.inner.is_passive() + } + + /// Is the verification request ready to start a verification flow. + pub fn is_ready(&self) -> bool { + self.inner.is_ready() + } + + /// Did we initiate the verification flow. + pub fn we_started(&self) -> bool { + self.inner.we_started() + } + + /// Get the user id of the other user participating in this verification + /// flow. + pub fn other_user_id(&self) -> &ruma::UserId { + self.inner.other_user() + } + + /// Is this a verification that is veryfying one of our own devices. + pub fn is_self_verification(&self) -> bool { + self.inner.is_self_verification() + } + + /// Get the supported verification methods of the other side. + /// + /// Will be present only if the other side requested the verification or if + /// we're in the ready state. + pub fn their_supported_methods(&self) -> Option> { + self.inner.their_supported_methods() + } + + /// Accept the verification request. + /// + /// This method will accept the request and signal that it supports the + /// `m.sas.v1`, the `m.qr_code.show.v1`, and `m.reciprocate.v1` method. + /// + /// If QR code scanning should be supported or QR code showing shouldn't be + /// supported the [`accept_with_methods()`] method should be used instead. + /// + /// [`accept_with_methods()`]: #method.accept_with_methods + pub async fn accept(&self) -> Result<()> { + if let Some(request) = self.inner.accept() { + self.client.send_verification_request(request).await?; + } + + Ok(()) + } + + /// Accept the verification request signaling that our client supports the + /// given verification methods. + /// + /// # Arguments + /// + /// * `methods` - The methods that we should advertise as supported by us. + pub async fn accept_with_methods(&self, methods: Vec) -> Result<()> { + if let Some(request) = self.inner.accept_with_methods(methods) { + self.client.send_verification_request(request).await?; + } + + Ok(()) + } + + /// Generate a QR code + pub async fn generate_qr_code(&self) -> Result> { + Ok(self + .inner + .generate_qr_code() + .await? + .map(|qr| QrVerification { inner: qr, client: self.client.clone() })) + } + + /// Transition from this verification request into a SAS verification flow. + pub async fn start_sas(&self) -> Result> { + if let Some((sas, request)) = self.inner.start_sas().await? { + self.client.send_verification_request(request).await?; + + Ok(Some(SasVerification { inner: sas, client: self.client.clone() })) + } else { + Ok(None) + } + } + + /// Cancel the verification request + pub async fn cancel(&self) -> Result<()> { + if let Some(request) = self.inner.cancel() { + self.client.send_verification_request(request).await?; + } + + Ok(()) + } +} diff --git a/matrix_sdk/src/sas.rs b/matrix_sdk/src/verification/sas.rs similarity index 62% rename from matrix_sdk/src/sas.rs rename to matrix_sdk/src/verification/sas.rs index 42949feb..3cb46641 100644 --- a/matrix_sdk/src/sas.rs +++ b/matrix_sdk/src/verification/sas.rs @@ -12,20 +12,19 @@ // See the License for the specific language governing permissions and // limitations under the License. -use matrix_sdk_base::crypto::{ - AcceptSettings, OutgoingVerificationRequest, ReadOnlyDevice, Sas as BaseSas, -}; +use matrix_sdk_base::crypto::{AcceptSettings, CancelInfo, ReadOnlyDevice, Sas as BaseSas}; +use ruma::UserId; use crate::{error::Result, Client}; /// An object controlling the interactive verification flow. #[derive(Debug, Clone)] -pub struct Sas { +pub struct SasVerification { pub(crate) inner: BaseSas, pub(crate) client: Client, } -impl Sas { +impl SasVerification { /// Accept the interactive verification flow. pub async fn accept(&self) -> Result<()> { self.accept_with_settings(Default::default()).await @@ -43,14 +42,21 @@ impl Sas { /// # use matrix_sdk::Client; /// # use futures::executor::block_on; /// # use url::Url; - /// use matrix_sdk::Sas; + /// # use ruma::user_id; + /// use matrix_sdk::verification::SasVerification; /// use matrix_sdk_base::crypto::AcceptSettings; - /// use matrix_sdk::events::key::verification::ShortAuthenticationString; + /// use matrix_sdk::ruma::events::key::verification::ShortAuthenticationString; /// # let homeserver = Url::parse("http://example.com").unwrap(); /// # let client = Client::new(homeserver).unwrap(); /// # let flow_id = "someID"; + /// # let user_id = user_id!("@alice:example"); /// # block_on(async { - /// let sas = client.get_verification(flow_id).await.unwrap(); + /// let sas = client + /// .get_verification(&user_id, flow_id) + /// .await + /// .unwrap() + /// .sas() + /// .unwrap(); /// /// let only_decimal = AcceptSettings::with_allowed_methods( /// vec![ShortAuthenticationString::Decimal] @@ -59,15 +65,8 @@ impl Sas { /// # }); /// ``` pub async fn accept_with_settings(&self, settings: AcceptSettings) -> Result<()> { - if let Some(req) = self.inner.accept_with_settings(settings) { - match req { - OutgoingVerificationRequest::ToDevice(r) => { - self.client.send_to_device(&r).await?; - } - OutgoingVerificationRequest::InRoom(r) => { - self.client.room_send_helper(&r).await?; - } - } + if let Some(request) = self.inner.accept_with_settings(settings) { + self.client.send_verification_request(request).await?; } Ok(()) } @@ -76,16 +75,8 @@ impl Sas { pub async fn confirm(&self) -> Result<()> { let (request, signature) = self.inner.confirm().await?; - match request { - Some(OutgoingVerificationRequest::InRoom(r)) => { - self.client.room_send_helper(&r).await?; - } - - Some(OutgoingVerificationRequest::ToDevice(r)) => { - self.client.send_to_device(&r).await?; - } - - None => (), + if let Some(request) = request { + self.client.send_verification_request(request).await?; } if let Some(s) = signature { @@ -98,14 +89,7 @@ impl Sas { /// Cancel the interactive verification flow. pub async fn cancel(&self) -> Result<()> { if let Some(request) = self.inner.cancel() { - match request { - OutgoingVerificationRequest::ToDevice(r) => { - self.client.send_to_device(&r).await?; - } - OutgoingVerificationRequest::InRoom(r) => { - self.client.room_send_helper(&r).await?; - } - } + self.client.send_verification_request(request).await?; } Ok(()) @@ -132,6 +116,22 @@ impl Sas { self.inner.is_done() } + /// Are we in a state where we can show the short auth string. + pub fn can_be_presented(&self) -> bool { + self.inner.can_be_presented() + } + + /// Did we initiate the verification flow. + pub fn we_started(&self) -> bool { + self.inner.we_started() + } + + /// Get info about the cancellation if the verification flow has been + /// cancelled. + pub fn cancel_info(&self) -> Option { + self.inner.cancel_info() + } + /// Is the verification process canceled. pub fn is_cancelled(&self) -> bool { self.inner.is_cancelled() @@ -141,4 +141,25 @@ impl Sas { pub fn other_device(&self) -> &ReadOnlyDevice { self.inner.other_device() } + + /// Did this verification flow start from a verification request. + pub fn started_from_request(&self) -> bool { + self.inner.started_from_request() + } + + /// Is this a verification that is veryfying one of our own devices. + pub fn is_self_verification(&self) -> bool { + self.inner.is_self_verification() + } + + /// Get our own user id. + pub fn own_user_id(&self) -> &UserId { + self.inner.user_id() + } + + /// Get the user id of the other user participating in this verification + /// flow. + pub fn other_user_id(&self) -> &UserId { + self.inner.other_user_id() + } } diff --git a/matrix_sdk/src/verification_request.rs b/matrix_sdk/src/verification_request.rs deleted file mode 100644 index 4d2773a2..00000000 --- a/matrix_sdk/src/verification_request.rs +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright 2020 The Matrix.org Foundation C.I.C. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -use matrix_sdk_base::crypto::{ - OutgoingVerificationRequest, VerificationRequest as BaseVerificationRequest, -}; - -use crate::{Client, Result}; - -/// An object controlling the interactive verification flow. -#[derive(Debug, Clone)] -pub struct VerificationRequest { - pub(crate) inner: BaseVerificationRequest, - pub(crate) client: Client, -} - -impl VerificationRequest { - /// Accept the verification request - pub async fn accept(&self) -> Result<()> { - if let Some(request) = self.inner.accept() { - match request { - OutgoingVerificationRequest::ToDevice(r) => { - self.client.send_to_device(&r).await?; - } - OutgoingVerificationRequest::InRoom(r) => { - self.client.room_send_helper(&r).await?; - } - }; - } - - Ok(()) - } - - /// Cancel the verification request - pub async fn cancel(&self) -> Result<()> { - todo!() - } -} diff --git a/matrix_sdk_appservice/Cargo.toml b/matrix_sdk_appservice/Cargo.toml index f14096c2..bb1d41c5 100644 --- a/matrix_sdk_appservice/Cargo.toml +++ b/matrix_sdk_appservice/Cargo.toml @@ -9,13 +9,10 @@ version = "0.1.0" [features] default = ["warp"] -actix = ["actix-rt", "actix-web"] -docs = ["actix", "warp"] +docs = ["warp"] [dependencies] -actix-rt = { version = "2", optional = true } -actix-web = { version = "4.0.0-beta.6", optional = true } dashmap = "4" futures = "0.3" futures-util = "0.3" @@ -29,10 +26,10 @@ tracing = "0.1" url = "2" warp = { git = "https://github.com/seanmonstar/warp.git", rev = "629405", optional = true, default-features = false } -matrix-sdk = { version = "0.2", path = "../matrix_sdk", default-features = false, features = ["appservice", "native-tls"] } +matrix-sdk = { version = "0.3", path = "../matrix_sdk", default-features = false, features = ["appservice", "native-tls"] } [dependencies.ruma] -version = "0.1.2" +version = "0.2.0" features = ["client-api-c", "appservice-api-s", "unstable-pre-spec"] [dev-dependencies] @@ -41,7 +38,7 @@ mockito = "0.30" tokio = { version = "1", default-features = false, features = ["rt-multi-thread", "macros"] } tracing-subscriber = "0.2" -matrix-sdk-test = { version = "0.2", path = "../matrix_sdk_test", features = ["appservice"] } +matrix-sdk-test = { version = "0.3", path = "../matrix_sdk_test", features = ["appservice"] } [[example]] name = "appservice_autojoin" diff --git a/matrix_sdk_appservice/examples/appservice_autojoin.rs b/matrix_sdk_appservice/examples/appservice_autojoin.rs index 0fbd99f7..f792f242 100644 --- a/matrix_sdk_appservice/examples/appservice_autojoin.rs +++ b/matrix_sdk_appservice/examples/appservice_autojoin.rs @@ -3,24 +3,26 @@ use std::{convert::TryFrom, env}; use matrix_sdk_appservice::{ matrix_sdk::{ async_trait, - events::{ - room::member::{MemberEventContent, MembershipState}, - SyncStateEvent, - }, - identifiers::UserId, room::Room, + ruma::{ + events::{ + room::member::{MemberEventContent, MembershipState}, + SyncStateEvent, + }, + UserId, + }, EventHandler, }, - Appservice, AppserviceRegistration, + AppService, AppServiceRegistration, }; use tracing::{error, trace}; -struct AppserviceEventHandler { - appservice: Appservice, +struct AppServiceEventHandler { + appservice: AppService, } -impl AppserviceEventHandler { - pub fn new(appservice: Appservice) -> Self { +impl AppServiceEventHandler { + pub fn new(appservice: AppService) -> Self { Self { appservice } } @@ -47,7 +49,7 @@ impl AppserviceEventHandler { } #[async_trait] -impl EventHandler for AppserviceEventHandler { +impl EventHandler for AppServiceEventHandler { async fn on_room_member(&self, room: Room, event: &SyncStateEvent) { match self.handle_room_member(room, event).await { Ok(_) => (), @@ -63,10 +65,10 @@ pub async fn main() -> Result<(), Box> { let homeserver_url = "http://localhost:8008"; let server_name = "localhost"; - let registration = AppserviceRegistration::try_from_yaml_file("./tests/registration.yaml")?; + let registration = AppServiceRegistration::try_from_yaml_file("./tests/registration.yaml")?; - let mut appservice = Appservice::new(homeserver_url, server_name, registration).await?; - appservice.set_event_handler(Box::new(AppserviceEventHandler::new(appservice.clone()))).await?; + let mut appservice = AppService::new(homeserver_url, server_name, registration).await?; + appservice.set_event_handler(Box::new(AppServiceEventHandler::new(appservice.clone()))).await?; let (host, port) = appservice.registration().get_host_and_port()?; appservice.run(host, port).await?; diff --git a/matrix_sdk_appservice/src/error.rs b/matrix_sdk_appservice/src/error.rs index 1e08c192..3bd1d4f0 100644 --- a/matrix_sdk_appservice/src/error.rs +++ b/matrix_sdk_appservice/src/error.rs @@ -70,19 +70,8 @@ pub enum Error { #[cfg(feature = "warp")] #[error("warp rejection: {0}")] WarpRejection(String), - - #[cfg(feature = "actix")] - #[error(transparent)] - Actix(#[from] actix_web::Error), - - #[cfg(feature = "actix")] - #[error(transparent)] - ActixPayload(#[from] actix_web::error::PayloadError), } -#[cfg(feature = "actix")] -impl actix_web::error::ResponseError for Error {} - #[cfg(feature = "warp")] impl warp::reject::Reject for Error {} diff --git a/matrix_sdk_appservice/src/lib.rs b/matrix_sdk_appservice/src/lib.rs index d94eff1e..125cb7f0 100644 --- a/matrix_sdk_appservice/src/lib.rs +++ b/matrix_sdk_appservice/src/lib.rs @@ -41,11 +41,11 @@ //! # #[async_trait] //! # impl EventHandler for MyEventHandler {} //! # -//! use matrix_sdk_appservice::{Appservice, AppserviceRegistration}; +//! use matrix_sdk_appservice::{AppService, AppServiceRegistration}; //! //! let homeserver_url = "http://127.0.0.1:8008"; //! let server_name = "localhost"; -//! let registration = AppserviceRegistration::try_from_yaml_str( +//! let registration = AppServiceRegistration::try_from_yaml_str( //! r" //! id: appservice //! url: http://127.0.0.1:9009 @@ -58,7 +58,7 @@ //! regex: '@_appservice_.*' //! ")?; //! -//! let mut appservice = Appservice::new(homeserver_url, server_name, registration).await?; +//! let mut appservice = AppService::new(homeserver_url, server_name, registration).await?; //! appservice.set_event_handler(Box::new(MyEventHandler)).await?; //! //! let (host, port) = appservice.registration().get_host_and_port()?; @@ -74,8 +74,8 @@ //! [matrix-org/matrix-rust-sdk#228]: https://github.com/matrix-org/matrix-rust-sdk/issues/228 //! [examples directory]: https://github.com/matrix-org/matrix-rust-sdk/tree/master/matrix_sdk_appservice/examples -#[cfg(not(any(feature = "actix", feature = "warp")))] -compile_error!("one webserver feature must be enabled. available ones: `actix`, `warp`"); +#[cfg(not(any(feature = "warp")))] +compile_error!("one webserver feature must be enabled. available ones: `warp`"); use std::{ convert::{TryFrom, TryInto}, @@ -89,12 +89,15 @@ use dashmap::DashMap; pub use error::Error; use http::{uri::PathAndQuery, Uri}; pub use matrix_sdk; -use matrix_sdk::{reqwest::Url, Bytes, Client, ClientConfig, EventHandler, HttpError, Session}; +#[doc(no_inline)] +pub use matrix_sdk::ruma; +use matrix_sdk::{ + bytes::Bytes, reqwest::Url, Client, ClientConfig, EventHandler, HttpError, Session, +}; use regex::Regex; -#[doc(inline)] -pub use ruma::api::{appservice as api, appservice::Registration}; use ruma::{ api::{ + appservice::Registration, client::{ error::ErrorKind, r0::{account::register, uiaa::UiaaResponse}, @@ -112,15 +115,15 @@ pub type Result = std::result::Result; pub type Host = String; pub type Port = u16; -/// Appservice Registration +/// AppService Registration /// /// Wrapper around [`Registration`] #[derive(Debug, Clone)] -pub struct AppserviceRegistration { +pub struct AppServiceRegistration { inner: Registration, } -impl AppserviceRegistration { +impl AppServiceRegistration { /// Try to load registration from yaml string /// /// See the fields of [`Registration`] for the required format @@ -158,13 +161,13 @@ impl AppserviceRegistration { } } -impl From for AppserviceRegistration { +impl From for AppServiceRegistration { fn from(value: Registration) -> Self { Self { inner: value } } } -impl Deref for AppserviceRegistration { +impl Deref for AppServiceRegistration { type Target = Registration; fn deref(&self) -> &Self::Target { @@ -175,7 +178,7 @@ impl Deref for AppserviceRegistration { type Localpart = String; /// The `localpart` of the user associated with the application service via -/// `sender_localpart` in [`AppserviceRegistration`]. +/// `sender_localpart` in [`AppServiceRegistration`]. /// /// Dummy type for shared documentation #[allow(dead_code)] @@ -183,23 +186,23 @@ pub type MainUser = (); /// The application service may specify the virtual user to act as through use /// of a user_id query string parameter on the request. The user specified in -/// the query string must be covered by one of the [`AppserviceRegistration`]'s +/// the query string must be covered by one of the [`AppServiceRegistration`]'s /// `users` namespaces. /// /// Dummy type for shared documentation pub type VirtualUser = (); -/// Appservice +/// AppService #[derive(Debug, Clone)] -pub struct Appservice { +pub struct AppService { homeserver_url: Url, server_name: ServerNameBox, - registration: Arc, + registration: Arc, clients: Arc>, } -impl Appservice { - /// Create new Appservice +impl AppService { + /// Create new AppService /// /// Also creates and caches a [`Client`] for the [`MainUser`]. /// The default [`ClientConfig`] is used, if you want to customize it @@ -210,14 +213,14 @@ impl Appservice { /// * `homeserver_url` - The homeserver that the client should connect to. /// * `server_name` - The server name to use when constructing user ids from /// the localpart. - /// * `registration` - The [Appservice Registration] to use when interacting + /// * `registration` - The [AppService Registration] to use when interacting /// with the homeserver. /// - /// [Appservice Registration]: https://matrix.org/docs/spec/application_service/r0.1.2#registration + /// [AppService Registration]: https://matrix.org/docs/spec/application_service/r0.1.2#registration pub async fn new( homeserver_url: impl TryInto, server_name: impl TryInto, - registration: AppserviceRegistration, + registration: AppServiceRegistration, ) -> Result { let appservice = Self::new_with_config( homeserver_url, @@ -235,7 +238,7 @@ impl Appservice { pub async fn new_with_config( homeserver_url: impl TryInto, server_name: impl TryInto, - registration: AppserviceRegistration, + registration: AppServiceRegistration, client_config: ClientConfig, ) -> Result { let homeserver_url = homeserver_url.try_into()?; @@ -244,7 +247,7 @@ impl Appservice { let clients = Arc::new(DashMap::new()); let sender_localpart = registration.sender_localpart.clone(); - let appservice = Appservice { homeserver_url, server_name, registration, clients }; + let appservice = AppService { homeserver_url, server_name, registration, clients }; // we create and cache the [`MainUser`] by default appservice.create_and_cache_client(&sender_localpart, client_config).await?; @@ -354,12 +357,12 @@ impl Appservice { /// Convenience wrapper around [`Client::set_event_handler()`] that attaches /// the event handler to the [`MainUser`]'s [`Client`] /// - /// Note that the event handler in the [`Appservice`] context only triggers + /// Note that the event handler in the [`AppService`] context only triggers /// [`join` room `timeline` events], so no state events or events from the /// `invite`, `knock` or `leave` scope. The rationale behind that is - /// that incoming Appservice transactions from the homeserver are not + /// that incoming AppService transactions from the homeserver are not /// necessarily bound to a specific user but can cover a multitude of - /// namespaces, and as such the Appservice basically only "observes + /// namespaces, and as such the AppService basically only "observes /// joined rooms". Also currently homeservers only push PDUs to appservices, /// no EDUs. There's the open [MSC2409] regarding supporting EDUs in the /// future, though it seems to be planned to put EDUs into a different @@ -410,10 +413,10 @@ impl Appservice { Ok(()) } - /// Get the Appservice [registration] + /// Get the AppService [registration] /// /// [registration]: https://matrix.org/docs/spec/application_service/r0.1.2#registration - pub fn registration(&self) -> &AppserviceRegistration { + pub fn registration(&self) -> &AppServiceRegistration { &self.registration } @@ -424,11 +427,11 @@ impl Appservice { self.registration.hs_token == hs_token.as_ref() } - /// Check if given `user_id` is in any of the [`AppserviceRegistration`]'s + /// Check if given `user_id` is in any of the [`AppServiceRegistration`]'s /// `users` namespaces pub fn user_id_is_in_namespace(&self, user_id: impl AsRef) -> Result { for user in &self.registration.namespaces.users { - // TODO: precompile on Appservice construction + // TODO: precompile on AppService construction let re = Regex::new(&user.regex)?; if re.is_match(user_id.as_ref()) { return Ok(true); @@ -438,24 +441,6 @@ impl Appservice { Ok(false) } - /// Returns a closure to be used with [`actix_web::App::configure()`] - /// - /// Note that if you handle any of the [application-service-specific - /// routes], including the legacy routes, you will break the appservice - /// functionality. - /// - /// [application-service-specific routes]: https://spec.matrix.org/unstable/application-service-api/#legacy-routes - #[cfg(feature = "actix")] - #[cfg_attr(docs, doc(cfg(feature = "actix")))] - pub fn actix_configure(&self) -> impl FnOnce(&mut actix_web::web::ServiceConfig) { - let appservice = self.clone(); - - move |config| { - config.data(appservice); - webserver::actix::configure(config); - } - } - /// Returns a [`warp::Filter`] to be used as [`warp::serve()`] route /// /// Note that if you handle any of the [application-service-specific @@ -477,13 +462,7 @@ impl Appservice { pub async fn run(&self, host: impl Into, port: impl Into) -> Result<()> { let host = host.into(); let port = port.into(); - info!("Starting Appservice on {}:{}", &host, &port); - - #[cfg(feature = "actix")] - { - webserver::actix::run_server(self.clone(), host, port).await?; - Ok(()) - } + info!("Starting AppService on {}:{}", &host, &port); #[cfg(feature = "warp")] { @@ -491,7 +470,7 @@ impl Appservice { Ok(()) } - #[cfg(not(any(feature = "actix", feature = "warp",)))] + #[cfg(not(any(feature = "warp",)))] unreachable!() } } diff --git a/matrix_sdk_appservice/src/webserver/actix.rs b/matrix_sdk_appservice/src/webserver/actix.rs deleted file mode 100644 index b983f92e..00000000 --- a/matrix_sdk_appservice/src/webserver/actix.rs +++ /dev/null @@ -1,149 +0,0 @@ -// Copyright 2021 Famedly GmbH -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -use std::pin::Pin; - -pub use actix_web::Scope; -use actix_web::{ - dev::Payload, - error::PayloadError, - get, put, - web::{self, BytesMut, Data}, - App, FromRequest, HttpRequest, HttpResponse, HttpServer, -}; -use futures::Future; -use futures_util::TryStreamExt; -use ruma::api::appservice as api; - -use crate::{error::Error, Appservice}; - -pub async fn run_server( - appservice: Appservice, - host: impl Into, - port: impl Into, -) -> Result<(), Error> { - HttpServer::new(move || App::new().configure(appservice.actix_configure())) - .bind((host.into(), port.into()))? - .run() - .await?; - - Ok(()) -} - -pub fn configure(config: &mut actix_web::web::ServiceConfig) { - // also handles legacy routes - config.service(push_transactions).service(query_user_id).service(query_room_alias).service( - web::scope("/_matrix/app/v1") - .service(push_transactions) - .service(query_user_id) - .service(query_room_alias), - ); -} - -#[tracing::instrument] -#[put("/transactions/{txn_id}")] -async fn push_transactions( - request: IncomingRequest, - appservice: Data, -) -> Result { - if !appservice.compare_hs_token(request.access_token) { - return Ok(HttpResponse::Unauthorized().finish()); - } - - appservice.get_cached_client(None)?.receive_transaction(request.incoming).await?; - - Ok(HttpResponse::Ok().json("{}")) -} - -#[tracing::instrument] -#[get("/users/{user_id}")] -async fn query_user_id( - request: IncomingRequest, - appservice: Data, -) -> Result { - if !appservice.compare_hs_token(request.access_token) { - return Ok(HttpResponse::Unauthorized().finish()); - } - - Ok(HttpResponse::Ok().json("{}")) -} - -#[tracing::instrument] -#[get("/rooms/{room_alias}")] -async fn query_room_alias( - request: IncomingRequest, - appservice: Data, -) -> Result { - if !appservice.compare_hs_token(request.access_token) { - return Ok(HttpResponse::Unauthorized().finish()); - } - - Ok(HttpResponse::Ok().json("{}")) -} - -#[derive(Debug)] -pub struct IncomingRequest { - access_token: String, - incoming: T, -} - -impl FromRequest for IncomingRequest { - type Error = Error; - type Future = Pin>>>; - type Config = (); - - fn from_request(request: &HttpRequest, payload: &mut Payload) -> Self::Future { - let request = request.to_owned(); - let payload = payload.take(); - - Box::pin(async move { - let mut builder = - http::request::Builder::new().method(request.method()).uri(request.uri()); - - let headers = builder.headers_mut().ok_or(Error::UnknownHttpRequestBuilder)?; - for (key, value) in request.headers().iter() { - headers.append(key, value.to_owned()); - } - - let bytes = payload - .try_fold(BytesMut::new(), |mut body, chunk| async move { - body.extend_from_slice(&chunk); - Ok::<_, PayloadError>(body) - }) - .await? - .into(); - - let access_token = match request.uri().query() { - Some(query) => { - let query: Vec<(String, String)> = ruma::serde::urlencoded::from_str(query)?; - query.into_iter().find(|(key, _)| key == "access_token").map(|(_, value)| value) - } - None => None, - }; - - let access_token = match access_token { - Some(access_token) => access_token, - None => return Err(Error::MissingAccessToken), - }; - - let request = builder.body(bytes)?; - let request = crate::transform_legacy_route(request)?; - - Ok(IncomingRequest { - access_token, - incoming: ruma::api::IncomingRequest::try_from_http_request(request)?, - }) - }) - } -} diff --git a/matrix_sdk_appservice/src/webserver/mod.rs b/matrix_sdk_appservice/src/webserver/mod.rs index a0880ec6..0b8d1141 100644 --- a/matrix_sdk_appservice/src/webserver/mod.rs +++ b/matrix_sdk_appservice/src/webserver/mod.rs @@ -1,4 +1,2 @@ -#[cfg(feature = "actix")] -pub mod actix; #[cfg(feature = "warp")] pub mod warp; diff --git a/matrix_sdk_appservice/src/webserver/warp.rs b/matrix_sdk_appservice/src/webserver/warp.rs index 9a4f3504..8e105252 100644 --- a/matrix_sdk_appservice/src/webserver/warp.rs +++ b/matrix_sdk_appservice/src/webserver/warp.rs @@ -15,14 +15,14 @@ use std::{net::ToSocketAddrs, result::Result as StdResult}; use futures::TryFutureExt; -use matrix_sdk::Bytes; +use matrix_sdk::{bytes::Bytes, ruma}; use serde::Serialize; use warp::{filters::BoxedFilter, path::FullPath, Filter, Rejection, Reply}; -use crate::{Appservice, Error, Result}; +use crate::{AppService, Error, Result}; pub async fn run_server( - appservice: Appservice, + appservice: AppService, host: impl Into, port: impl Into, ) -> Result<()> { @@ -37,7 +37,7 @@ pub async fn run_server( } } -pub fn warp_filter(appservice: Appservice) -> BoxedFilter<(impl Reply,)> { +pub fn warp_filter(appservice: AppService) -> BoxedFilter<(impl Reply,)> { // TODO: try to use a struct instead of needlessly cloning appservice multiple // times on every request warp::any() @@ -51,7 +51,7 @@ pub fn warp_filter(appservice: Appservice) -> BoxedFilter<(impl Reply,)> { mod filters { use super::*; - pub fn users(appservice: Appservice) -> BoxedFilter<(impl Reply,)> { + pub fn users(appservice: AppService) -> BoxedFilter<(impl Reply,)> { warp::get() .and( warp::path!("_matrix" / "app" / "v1" / "users" / String) @@ -65,7 +65,7 @@ mod filters { .boxed() } - pub fn rooms(appservice: Appservice) -> BoxedFilter<(impl Reply,)> { + pub fn rooms(appservice: AppService) -> BoxedFilter<(impl Reply,)> { warp::get() .and( warp::path!("_matrix" / "app" / "v1" / "rooms" / String) @@ -79,7 +79,7 @@ mod filters { .boxed() } - pub fn transactions(appservice: Appservice) -> BoxedFilter<(impl Reply,)> { + pub fn transactions(appservice: AppService) -> BoxedFilter<(impl Reply,)> { warp::put() .and( warp::path!("_matrix" / "app" / "v1" / "transactions" / String) @@ -93,7 +93,7 @@ mod filters { .boxed() } - fn common(appservice: Appservice) -> BoxedFilter<(Appservice, http::Request)> { + fn common(appservice: AppService) -> BoxedFilter<(AppService, http::Request)> { warp::any() .and(filters::valid_access_token(appservice.registration().hs_token.clone())) .map(move || appservice.clone()) @@ -110,7 +110,7 @@ mod filters { .and(warp::query::raw()) .and_then(|token: String, query: String| async move { let query: Vec<(String, String)> = - matrix_sdk::urlencoded::from_str(&query).map_err(Error::from)?; + ruma::serde::urlencoded::from_str(&query).map_err(Error::from)?; if query.into_iter().any(|(key, value)| key == "access_token" && value == token) { Ok::<(), Rejection>(()) @@ -156,7 +156,7 @@ mod handlers { pub async fn user( _user_id: String, - _appservice: Appservice, + _appservice: AppService, _request: http::Request, ) -> StdResult { Ok(warp::reply::json(&String::from("{}"))) @@ -164,7 +164,7 @@ mod handlers { pub async fn room( _room_id: String, - _appservice: Appservice, + _appservice: AppService, _request: http::Request, ) -> StdResult { Ok(warp::reply::json(&String::from("{}"))) @@ -172,11 +172,11 @@ mod handlers { pub async fn transaction( _txn_id: String, - appservice: Appservice, + appservice: AppService, request: http::Request, ) -> StdResult { - let incoming_transaction: matrix_sdk::api_appservice::event::push_events::v1::IncomingRequest = - matrix_sdk::IncomingRequest::try_from_http_request(request).map_err(Error::from)?; + let incoming_transaction: ruma::api::appservice::event::push_events::v1::IncomingRequest = + ruma::api::IncomingRequest::try_from_http_request(request).map_err(Error::from)?; let client = appservice.get_cached_client(None)?; client.receive_transaction(incoming_transaction).map_err(Error::from).await?; diff --git a/matrix_sdk_appservice/tests/tests.rs b/matrix_sdk_appservice/tests/tests.rs index f407b1c0..aebb04f8 100644 --- a/matrix_sdk_appservice/tests/tests.rs +++ b/matrix_sdk_appservice/tests/tests.rs @@ -1,12 +1,12 @@ use std::sync::{Arc, Mutex}; -#[cfg(feature = "actix")] -use actix_web::{test as actix_test, App as ActixApp, HttpResponse}; use matrix_sdk::{ - api_appservice::Registration, async_trait, - events::{room::member::MemberEventContent, SyncStateEvent}, room::Room, + ruma::{ + api::appservice::Registration, + events::{room::member::MemberEventContent, SyncStateEvent}, + }, ClientConfig, EventHandler, RequestConfig, }; use matrix_sdk_appservice::*; @@ -19,16 +19,16 @@ fn registration_string() -> String { include_str!("../tests/registration.yaml").to_owned() } -async fn appservice(registration: Option) -> Result { +async fn appservice(registration: Option) -> Result { // env::set_var( // "RUST_LOG", - // "mockito=debug,matrix_sdk=debug,ruma=debug,actix_web=debug,warp=debug", + // "mockito=debug,matrix_sdk=debug,ruma=debug,warp=debug", // ); let _ = tracing_subscriber::fmt::try_init(); let registration = match registration { Some(registration) => registration.into(), - None => AppserviceRegistration::try_from_yaml_str(registration_string()).unwrap(), + None => AppServiceRegistration::try_from_yaml_str(registration_string()).unwrap(), }; let homeserver_url = mockito::server_url(); @@ -37,7 +37,7 @@ async fn appservice(registration: Option) -> Result { let client_config = ClientConfig::default().request_config(RequestConfig::default().disable_retry()); - Ok(Appservice::new_with_config( + Ok(AppService::new_with_config( homeserver_url.as_ref(), server_name, registration, @@ -97,16 +97,6 @@ async fn test_put_transaction() -> Result<()> { .into_response() .status(); - #[cfg(feature = "actix")] - let status = { - let app = - actix_test::init_service(ActixApp::new().configure(appservice.actix_configure())).await; - - let req = actix_test::TestRequest::put().uri(uri).set_json(&transaction).to_request(); - - actix_test::call_service(&app, req).await.status() - }; - assert_eq!(status, 200); Ok(()) @@ -128,16 +118,6 @@ async fn test_get_user() -> Result<()> { .into_response() .status(); - #[cfg(feature = "actix")] - let status = { - let app = - actix_test::init_service(ActixApp::new().configure(appservice.actix_configure())).await; - - let req = actix_test::TestRequest::get().uri(uri).to_request(); - - actix_test::call_service(&app, req).await.status() - }; - assert_eq!(status, 200); Ok(()) @@ -159,16 +139,6 @@ async fn test_get_room() -> Result<()> { .into_response() .status(); - #[cfg(feature = "actix")] - let status = { - let app = - actix_test::init_service(ActixApp::new().configure(appservice.actix_configure())).await; - - let req = actix_test::TestRequest::get().uri(uri).to_request(); - - actix_test::call_service(&app, req).await.status() - }; - assert_eq!(status, 200); Ok(()) @@ -195,16 +165,6 @@ async fn test_invalid_access_token() -> Result<()> { .into_response() .status(); - #[cfg(feature = "actix")] - let status = { - let app = - actix_test::init_service(ActixApp::new().configure(appservice.actix_configure())).await; - - let req = actix_test::TestRequest::put().uri(uri).set_json(&transaction).to_request(); - - actix_test::call_service(&app, req).await.status() - }; - assert_eq!(status, 401); Ok(()) @@ -235,20 +195,6 @@ async fn test_no_access_token() -> Result<()> { assert_eq!(status, 401); } - #[cfg(feature = "actix")] - { - let app = - actix_test::init_service(ActixApp::new().configure(appservice.actix_configure())).await; - - let req = actix_test::TestRequest::put().uri(uri).set_json(&transaction).to_request(); - - let resp = actix_test::call_service(&app, req).await; - - // TODO: this should actually return a 401 but is 500 because something in the - // extractor fails - assert_eq!(resp.status(), 500); - } - Ok(()) } @@ -294,16 +240,6 @@ async fn test_event_handler() -> Result<()> { .await .unwrap(); - #[cfg(feature = "actix")] - { - let app = - actix_test::init_service(ActixApp::new().configure(appservice.actix_configure())).await; - - let req = actix_test::TestRequest::put().uri(uri).set_json(&transaction).to_request(); - - actix_test::call_service(&app, req).await; - }; - let on_room_member_called = *example.on_state_member.lock().unwrap(); assert!(on_room_member_called); @@ -330,20 +266,6 @@ async fn test_unrelated_path() -> Result<()> { response.status() }; - #[cfg(feature = "actix")] - let status = { - let app = actix_test::init_service( - ActixApp::new() - .configure(appservice.actix_configure()) - .route("/unrelated", actix_web::web::get().to(HttpResponse::Ok)), - ) - .await; - - let req = actix_test::TestRequest::get().uri("/unrelated").to_request(); - - actix_test::call_service(&app, req).await.status() - }; - assert_eq!(status, 200); Ok(()) @@ -355,7 +277,7 @@ mod registration { #[test] fn test_registration() -> Result<()> { let registration: Registration = serde_yaml::from_str(®istration_string())?; - let registration: AppserviceRegistration = registration.into(); + let registration: AppServiceRegistration = registration.into(); assert_eq!(registration.id, "appservice"); @@ -364,7 +286,7 @@ mod registration { #[test] fn test_registration_from_yaml_file() -> Result<()> { - let registration = AppserviceRegistration::try_from_yaml_file("./tests/registration.yaml")?; + let registration = AppServiceRegistration::try_from_yaml_file("./tests/registration.yaml")?; assert_eq!(registration.id, "appservice"); @@ -373,7 +295,7 @@ mod registration { #[test] fn test_registration_from_yaml_str() -> Result<()> { - let registration = AppserviceRegistration::try_from_yaml_str(registration_string())?; + let registration = AppServiceRegistration::try_from_yaml_str(registration_string())?; assert_eq!(registration.id, "appservice"); diff --git a/matrix_sdk_base/Cargo.toml b/matrix_sdk_base/Cargo.toml index 2adcac7a..cd7b08fb 100644 --- a/matrix_sdk_base/Cargo.toml +++ b/matrix_sdk_base/Cargo.toml @@ -8,7 +8,7 @@ license = "Apache-2.0" name = "matrix-sdk-base" readme = "README.md" repository = "https://github.com/matrix-org/matrix-rust-sdk" -version = "0.2.0" +version = "0.3.0" [package.metadata.docs.rs] features = ["docs"] @@ -26,44 +26,44 @@ docs = ["encryption", "sled_cryptostore"] [dependencies] dashmap = "4.0.2" lru = "0.6.5" -ruma = { version = "0.1.2", features = ["client-api-c", "unstable-pre-spec"] } -serde = { version = "1.0.122", features = ["rc"] } -serde_json = "1.0.61" -tracing = "0.1.22" +ruma = { version = "0.2.0", features = ["client-api-c", "unstable-pre-spec"] } +serde = { version = "1.0.126", features = ["rc"] } +serde_json = "1.0.64" +tracing = "0.1.26" -matrix-sdk-common = { version = "0.2.0", path = "../matrix_sdk_common" } -matrix-sdk-crypto = { version = "0.2.0", path = "../matrix_sdk_crypto", optional = true } +matrix-sdk-common = { version = "0.3.0", path = "../matrix_sdk_common" } +matrix-sdk-crypto = { version = "0.3.0", path = "../matrix_sdk_crypto", optional = true } # Misc dependencies -thiserror = "1.0.23" -futures = "0.3.12" -zeroize = { version = "1.2.0", features = ["zeroize_derive"] } +thiserror = "1.0.25" +futures = "0.3.15" +zeroize = { version = "1.3.0", features = ["zeroize_derive"] } # Deps for the sled state store sled = { version = "0.34.6", optional = true } -chacha20poly1305 = { version = "0.7.1", optional = true } -pbkdf2 = { version = "0.6.0", default-features = false, optional = true } -hmac = { version = "0.10.1", optional = true } -sha2 = { version = "0.9.2", optional = true } -rand = { version = "0.8.2", optional = true } +chacha20poly1305 = { version = "0.8.0", optional = true } +pbkdf2 = { version = "0.8.0", default-features = false, optional = true } +hmac = { version = "0.11.0", optional = true } +sha2 = { version = "0.9.5", optional = true } +rand = { version = "0.8.4", optional = true } [target.'cfg(not(target_arch = "wasm32"))'.dependencies.tokio] -version = "1.1.0" +version = "1.7.1" default-features = false features = ["sync", "fs"] [dev-dependencies] -matrix-sdk-test = { version = "0.2.0", path = "../matrix_sdk_test" } -http = "0.2.3" +matrix-sdk-test = { version = "0.3.0", path = "../matrix_sdk_test" } +http = "0.2.4" [target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies] -tokio = { version = "1.1.0", default-features = false, features = ["rt-multi-thread", "macros"] } +tokio = { version = "1.7.1", default-features = false, features = ["rt-multi-thread", "macros"] } tempfile = "3.2.0" -rustyline = "7.1.0" +rustyline = "8.2.0" rustyline-derive = "0.4.0" atty = "0.2.14" clap = "2.33.3" syntect = "4.5.0" [target.'cfg(target_arch = "wasm32")'.dev-dependencies] -wasm-bindgen-test = "0.3.19" +wasm-bindgen-test = "0.3.24" diff --git a/matrix_sdk_base/examples/state_inspector.rs b/matrix_sdk_base/examples/state_inspector.rs index 5944104c..6aefc443 100644 --- a/matrix_sdk_base/examples/state_inspector.rs +++ b/matrix_sdk_base/examples/state_inspector.rs @@ -6,10 +6,7 @@ use atty::Stream; use clap::{App as Argparse, AppSettings as ArgParseSettings, Arg, ArgMatches, SubCommand}; use futures::executor::block_on; use matrix_sdk_base::{RoomInfo, Store}; -use ruma::{ - events::EventType, - identifiers::{RoomId, UserId}, -}; +use ruma::{events::EventType, RoomId, UserId}; #[cfg(not(target_arch = "wasm32"))] use rustyline::{ completion::{Completer, Pair}, diff --git a/matrix_sdk_base/src/client.rs b/matrix_sdk_base/src/client.rs index 78853077..399b5348 100644 --- a/matrix_sdk_base/src/client.rs +++ b/matrix_sdk_base/src/client.rs @@ -36,7 +36,7 @@ use matrix_sdk_common::{locks::Mutex, uuid::Uuid}; use matrix_sdk_crypto::{ store::{CryptoStore, CryptoStoreError}, Device, EncryptionSettings, IncomingResponse, MegolmError, OlmError, OlmMachine, - OutgoingRequest, Sas, ToDeviceRequest, UserDevices, + OutgoingRequest, ToDeviceRequest, UserDevices, }; #[cfg(feature = "encryption")] use ruma::{ @@ -89,8 +89,8 @@ pub struct AdditionalUnsignedData { pub prev_content: Option>, } -/// Transform state event by hoisting `prev_content` field from `unsigned` to -/// the top level. +/// Transform an `AnySyncStateEvent` by hoisting `prev_content` field from +/// `unsigned` to the top level. /// /// Due to a [bug in synapse][synapse-bug], `prev_content` often ends up in /// `unsigned` contrary to the C2S spec. Some more discussion can be found @@ -129,7 +129,17 @@ fn hoist_member_event( Ok(e) } -fn hoist_room_event_prev_content( +/// Transform an `AnySyncRoomEvent` by hoisting `prev_content` field from +/// `unsigned` to the top level. +/// +/// Due to a [bug in synapse][synapse-bug], `prev_content` often ends up in +/// `unsigned` contrary to the C2S spec. Some more discussion can be found +/// [here][discussion]. Until this is fixed in synapse or handled in Ruma, we +/// use this to hoist up `prev_content` to the top level. +/// +/// [synapse-bug]: +/// [discussion]: +pub fn hoist_room_event_prev_content( event: &Raw, ) -> StdResult { let prev_content = event @@ -1202,21 +1212,6 @@ impl BaseClient { } } - /// Get a `Sas` verification object with the given flow id. - /// - /// # Arguments - /// - /// * `flow_id` - The unique id that identifies a interactive verification - /// flow. For in-room verifications this will be the event id of the - /// *m.key.verification.request* event that started the flow, for the - /// to-device verification flows this will be the transaction id of the - /// *m.key.verification.start* event. - #[cfg(feature = "encryption")] - #[cfg_attr(feature = "docs", doc(cfg(encryption)))] - pub async fn get_verification(&self, flow_id: &str) -> Option { - self.olm.lock().await.as_ref().and_then(|o| o.get_verification(flow_id)) - } - /// Get a specific device of a user. /// /// # Arguments diff --git a/matrix_sdk_base/src/lib.rs b/matrix_sdk_base/src/lib.rs index 326700ce..051f2181 100644 --- a/matrix_sdk_base/src/lib.rs +++ b/matrix_sdk_base/src/lib.rs @@ -50,7 +50,9 @@ mod rooms; mod session; mod store; -pub use client::{BaseClient, BaseClientConfig}; +pub use client::{ + hoist_and_deserialize_state_event, hoist_room_event_prev_content, BaseClient, BaseClientConfig, +}; #[cfg(feature = "encryption")] #[cfg_attr(feature = "docs", doc(cfg(encryption)))] pub use matrix_sdk_crypto as crypto; diff --git a/matrix_sdk_base/src/store/memory_store.rs b/matrix_sdk_base/src/store/memory_store.rs index fb65d995..311e6373 100644 --- a/matrix_sdk_base/src/store/memory_store.rs +++ b/matrix_sdk_base/src/store/memory_store.rs @@ -28,9 +28,9 @@ use ruma::{ AnyGlobalAccountDataEvent, AnyRoomAccountDataEvent, AnyStrippedStateEvent, AnySyncStateEvent, EventType, }, - identifiers::{EventId, MxcUri, RoomId, UserId}, receipt::ReceiptType, serde::Raw, + EventId, MxcUri, RoomId, UserId, }; use tracing::info; @@ -69,7 +69,7 @@ pub struct MemoryStore { } impl MemoryStore { - #[cfg(not(feature = "sled_state_store"))] + #[allow(dead_code)] pub fn new() -> Self { Self { sync_token: Arc::new(RwLock::new(None)), @@ -581,15 +581,12 @@ impl StateStore for MemoryStore { } #[cfg(test)] -#[cfg(not(feature = "sled_state_store"))] mod test { - use matrix_sdk_common::{ - api::client::r0::media::get_content_thumbnail::Method, - identifiers::{event_id, mxc_uri, room_id, user_id, UserId}, - receipt::ReceiptType, - uint, - }; use matrix_sdk_test::async_test; + use ruma::{ + api::client::r0::media::get_content_thumbnail::Method, event_id, mxc_uri, + receipt::ReceiptType, room_id, uint, user_id, UserId, + }; use serde_json::json; use super::{MemoryStore, StateChanges}; diff --git a/matrix_sdk_base/src/store/sled_store/mod.rs b/matrix_sdk_base/src/store/sled_store/mod.rs index c99a4c4c..ea7db5c2 100644 --- a/matrix_sdk_base/src/store/sled_store/mod.rs +++ b/matrix_sdk_base/src/store/sled_store/mod.rs @@ -732,6 +732,7 @@ impl SledStore { .map(|u| { u.map_err(StoreError::Sled).and_then(|(key, value)| { self.deserialize_event(&value) + // TODO remove this unwrapping .map(|receipt| { (decode_key_value(&key, 3).unwrap().try_into().unwrap(), receipt) }) @@ -922,6 +923,7 @@ mod test { use matrix_sdk_test::async_test; use ruma::{ api::client::r0::media::get_content_thumbnail::Method, + event_id, events::{ room::{ member::{MemberEventContent, MembershipState}, @@ -929,10 +931,11 @@ mod test { }, AnySyncStateEvent, EventType, Unsigned, }, - identifiers::{event_id, mxc_uri, room_id, user_id, EventId, UserId}, + mxc_uri, receipt::ReceiptType, + room_id, serde::Raw, - uint, MilliSecondsSinceUnixEpoch, + uint, user_id, EventId, MilliSecondsSinceUnixEpoch, UserId, }; use serde_json::json; diff --git a/matrix_sdk_common/Cargo.toml b/matrix_sdk_common/Cargo.toml index 4839c98a..2a003cf8 100644 --- a/matrix_sdk_common/Cargo.toml +++ b/matrix_sdk_common/Cargo.toml @@ -8,24 +8,24 @@ license = "Apache-2.0" name = "matrix-sdk-common" readme = "README.md" repository = "https://github.com/matrix-org/matrix-rust-sdk" -version = "0.2.0" +version = "0.3.0" [dependencies] -async-trait = "0.1.42" +async-trait = "0.1.50" instant = { version = "0.1.9", features = ["wasm-bindgen", "now"] } -ruma = { version = "0.1.2", features = ["client-api-c"] } -serde = "1.0.122" +ruma = { version = "0.2.0", features = ["client-api-c"] } +serde = "1.0.126" [target.'cfg(not(target_arch = "wasm32"))'.dependencies] uuid = { version = "0.8.2", default-features = false, features = ["v4", "serde"] } [target.'cfg(not(target_arch = "wasm32"))'.dependencies.tokio] -version = "1.1.0" +version = "1.7.1" default-features = false features = ["rt", "sync"] [target.'cfg(target_arch = "wasm32")'.dependencies] -futures = "0.3.12" +futures = "0.3.15" futures-locks = { version = "0.6.0", default-features = false } -wasm-bindgen-futures = "0.4" +wasm-bindgen-futures = "0.4.24" uuid = { version = "0.8.2", default-features = false, features = ["v4", "wasm-bindgen"] } diff --git a/matrix_sdk_common/src/deserialized_responses.rs b/matrix_sdk_common/src/deserialized_responses.rs index 1a8c28af..24b07dd8 100644 --- a/matrix_sdk_common/src/deserialized_responses.rs +++ b/matrix_sdk_common/src/deserialized_responses.rs @@ -12,9 +12,8 @@ use ruma::{ room::member::MemberEventContent, AnySyncRoomEvent, StateEvent, StrippedStateEvent, SyncStateEvent, Unsigned, }, - identifiers::{DeviceKeyAlgorithm, EventId, RoomId, UserId}, serde::Raw, - DeviceIdBox, MilliSecondsSinceUnixEpoch, + DeviceIdBox, DeviceKeyAlgorithm, EventId, MilliSecondsSinceUnixEpoch, RoomId, UserId, }; use serde::{Deserialize, Serialize}; diff --git a/matrix_sdk_crypto/Cargo.toml b/matrix_sdk_crypto/Cargo.toml index 764b1f2c..80d168e4 100644 --- a/matrix_sdk_crypto/Cargo.toml +++ b/matrix_sdk_crypto/Cargo.toml @@ -8,7 +8,7 @@ license = "Apache-2.0" name = "matrix-sdk-crypto" readme = "README.md" repository = "https://github.com/matrix-org/matrix-rust-sdk" -version = "0.2.0" +version = "0.3.0" [package.metadata.docs.rs] features = ["docs"] @@ -20,42 +20,44 @@ sled_cryptostore = ["sled"] docs = ["sled_cryptostore"] [dependencies] -matrix-sdk-common = { version = "0.2.0", path = "../matrix_sdk_common" } -ruma = { version = "0.1.2", features = ["client-api-c", "unstable-pre-spec"] } +matrix-qrcode = { version = "0.1.0", path = "../matrix_qrcode" } +matrix-sdk-common = { version = "0.3.0", path = "../matrix_sdk_common" } +ruma = { version = "0.2.0", features = ["client-api-c", "unstable-pre-spec"] } -olm-rs = { version = "1.0.0", features = ["serde"] } -getrandom = "0.2.2" -serde = { version = "1.0.122", features = ["derive", "rc"] } -serde_json = "1.0.61" -zeroize = { version = "1.2.0", features = ["zeroize_derive"] } +olm-rs = { version = "1.0.1", features = ["serde"] } +getrandom = "0.2.3" +serde = { version = "1.0.126", features = ["derive", "rc"] } +serde_json = "1.0.64" +zeroize = { version = "1.3.0", features = ["zeroize_derive"] } # Misc dependencies -futures = "0.3.12" +futures = "0.3.15" sled = { version = "0.34.6", optional = true } -thiserror = "1.0.23" -tracing = "0.1.22" +thiserror = "1.0.25" +tracing = "0.1.26" atomic = "0.5.0" dashmap = "4.0.2" -sha2 = "0.9.2" -aes-gcm = "0.8.0" -aes-ctr = "0.6.0" -pbkdf2 = { version = "0.6.0", default-features = false } -hmac = "0.10.1" +sha2 = "0.9.5" +aes-gcm = "0.9.2" +aes = { version = "0.7.4", features = ["ctr"] } +pbkdf2 = { version = "0.8.0", default-features = false } +hmac = "0.11.0" base64 = "0.13.0" -byteorder = "1.4.2" +byteorder = "1.4.3" [dev-dependencies] -tokio = { version = "1.1.0", default-features = false, features = ["rt-multi-thread", "macros"] } -proptest = "0.10.1" -serde_json = "1.0.61" +tokio = { version = "1.7.1", default-features = false, features = ["rt-multi-thread", "macros"] } +proptest = "1.0.0" +matches = "0.1.8" +serde_json = "1.0.64" tempfile = "3.2.0" -http = "0.2.3" -matrix-sdk-test = { version = "0.2.0", path = "../matrix_sdk_test" } +http = "0.2.4" +matrix-sdk-test = { version = "0.3.0", path = "../matrix_sdk_test" } indoc = "1.0.3" criterion = { version = "0.3.4", features = ["async", "async_tokio", "html_reports"] } [target.'cfg(target_os = "linux")'.dev-dependencies] -pprof = { version = "0.4.2", features = ["flamegraph"] } +pprof = { version = "0.4.3", features = ["flamegraph"] } [[bench]] name = "crypto_bench" diff --git a/matrix_sdk_crypto/src/file_encryption/attachments.rs b/matrix_sdk_crypto/src/file_encryption/attachments.rs index d163c780..cdf82ab2 100644 --- a/matrix_sdk_crypto/src/file_encryption/attachments.rs +++ b/matrix_sdk_crypto/src/file_encryption/attachments.rs @@ -17,9 +17,9 @@ use std::{ io::{Error as IoError, ErrorKind, Read}, }; -use aes_ctr::{ - cipher::{NewStreamCipher, SyncStreamCipher}, - Aes256Ctr, +use aes::{ + cipher::{generic_array::GenericArray, FromBlockCipher, NewBlockCipher, StreamCipher}, + Aes256, Aes256Ctr, }; use base64::DecodeError; use getrandom::getrandom; @@ -37,17 +37,25 @@ const VERSION: &str = "v2"; /// A wrapper that transparently encrypts anything that implements `Read` as an /// Matrix attachment. -#[derive(Debug)] pub struct AttachmentDecryptor<'a, R: 'a + Read> { - inner_reader: &'a mut R, + inner: &'a mut R, expected_hash: Vec, sha: Sha256, aes: Aes256Ctr, } +impl<'a, R: 'a + Read + std::fmt::Debug> std::fmt::Debug for AttachmentDecryptor<'a, R> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("AttachmentDecryptor") + .field("inner", &self.inner) + .field("expected_hash", &self.expected_hash) + .finish() + } +} + impl<'a, R: Read> Read for AttachmentDecryptor<'a, R> { fn read(&mut self, buf: &mut [u8]) -> std::io::Result { - let read_bytes = self.inner_reader.read(buf)?; + let read_bytes = self.inner.read(buf)?; if read_bytes == 0 { let hash = self.sha.finalize_reset(); @@ -126,19 +134,20 @@ impl<'a, R: Read + 'a> AttachmentDecryptor<'a, R> { let hash = decode(info.hashes.get("sha256").ok_or(DecryptorError::MissingHash)?)?; let key = Zeroizing::from(decode_url_safe(info.web_key.k)?); let iv = decode(info.iv)?; + let iv = GenericArray::from_exact_iter(iv).ok_or(DecryptorError::KeyNonceLength)?; let sha = Sha256::default(); - let aes = Aes256Ctr::new_var(&key, &iv).map_err(|_| DecryptorError::KeyNonceLength)?; + let aes = Aes256::new_from_slice(&key).map_err(|_| DecryptorError::KeyNonceLength)?; + let aes = Aes256Ctr::from_block_cipher(aes, &iv); - Ok(AttachmentDecryptor { inner_reader: input, expected_hash: hash, sha, aes }) + Ok(AttachmentDecryptor { inner: input, expected_hash: hash, sha, aes }) } } /// A wrapper that transparently encrypts anything that implements `Read`. -#[derive(Debug)] pub struct AttachmentEncryptor<'a, R: Read + 'a> { finished: bool, - inner_reader: &'a mut R, + inner: &'a mut R, web_key: JsonWebKey, iv: String, hashes: BTreeMap, @@ -146,9 +155,18 @@ pub struct AttachmentEncryptor<'a, R: Read + 'a> { sha: Sha256, } +impl<'a, R: 'a + Read + std::fmt::Debug> std::fmt::Debug for AttachmentEncryptor<'a, R> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("AttachmentEncryptor") + .field("inner", &self.inner) + .field("finished", &self.finished) + .finish() + } +} + impl<'a, R: Read + 'a> Read for AttachmentEncryptor<'a, R> { fn read(&mut self, buf: &mut [u8]) -> std::io::Result { - let read_bytes = self.inner_reader.read(buf)?; + let read_bytes = self.inner.read(buf)?; if read_bytes == 0 { let hash = self.sha.finalize_reset(); @@ -209,12 +227,15 @@ impl<'a, R: Read + 'a> AttachmentEncryptor<'a, R> { ext: true, }); let encoded_iv = encode(&*iv); + let iv = GenericArray::from_slice(&*iv); + let key = GenericArray::from_slice(&*key); - let aes = Aes256Ctr::new_var(&*key, &*iv).expect("Cannot create AES encryption object."); + let aes = Aes256::new(key); + let aes = Aes256Ctr::from_block_cipher(aes, iv); AttachmentEncryptor { finished: false, - inner_reader: reader, + inner: reader, iv: encoded_iv, web_key, hashes: BTreeMap::new(), diff --git a/matrix_sdk_crypto/src/file_encryption/key_export.rs b/matrix_sdk_crypto/src/file_encryption/key_export.rs index 95cc8bb6..be191420 100644 --- a/matrix_sdk_crypto/src/file_encryption/key_export.rs +++ b/matrix_sdk_crypto/src/file_encryption/key_export.rs @@ -14,9 +14,9 @@ use std::io::{Cursor, Read, Seek, SeekFrom}; -use aes_ctr::{ - cipher::{NewStreamCipher, SyncStreamCipher}, - Aes256Ctr, +use aes::{ + cipher::{generic_array::GenericArray, FromBlockCipher, NewBlockCipher, StreamCipher}, + Aes256, Aes256Ctr, }; use byteorder::{BigEndian, ReadBytesExt}; use getrandom::getrandom; @@ -161,7 +161,12 @@ fn encrypt_helper(mut plaintext: &mut [u8], passphrase: &str, rounds: u32) -> St pbkdf2::>(passphrase.as_bytes(), &salt, rounds, &mut derived_keys); let (key, hmac_key) = derived_keys.split_at(KEY_SIZE); - let mut aes = Aes256Ctr::new_var(key, &iv.to_be_bytes()).expect("Can't create AES object"); + let key = GenericArray::from_slice(key); + let iv = iv.to_be_bytes(); + let iv = GenericArray::from_slice(&iv); + + let aes = Aes256::new(key); + let mut aes = Aes256Ctr::from_block_cipher(aes, iv); aes.apply_keystream(&mut plaintext); @@ -169,11 +174,11 @@ fn encrypt_helper(mut plaintext: &mut [u8], passphrase: &str, rounds: u32) -> St payload.extend(&VERSION.to_be_bytes()); payload.extend(&salt); - payload.extend(&iv.to_be_bytes()); + payload.extend(&*iv); payload.extend(&rounds.to_be_bytes()); payload.extend_from_slice(plaintext); - let mut hmac = Hmac::::new_varkey(hmac_key).expect("Can't create HMAC object"); + let mut hmac = Hmac::::new_from_slice(hmac_key).expect("Can't create HMAC object"); hmac.update(&payload); let mac = hmac.finalize(); @@ -213,12 +218,16 @@ fn decrypt_helper(ciphertext: &str, passphrase: &str) -> Result>(passphrase.as_bytes(), &salt, rounds, &mut derived_keys); let (key, hmac_key) = derived_keys.split_at(KEY_SIZE); - let mut hmac = Hmac::::new_varkey(hmac_key).expect("Can't create an HMAC object"); + let mut hmac = Hmac::::new_from_slice(hmac_key).expect("Can't create an HMAC object"); hmac.update(&decoded[0..ciphertext_end]); hmac.verify(&mac).map_err(|_| KeyExportError::InvalidMac)?; + let key = GenericArray::from_slice(key); + let iv = GenericArray::from_slice(&iv); + let mut ciphertext = &mut decoded[ciphertext_start..ciphertext_end]; - let mut aes = Aes256Ctr::new_var(key, &iv).expect("Can't create an AES object"); + let aes = Aes256::new(key); + let mut aes = Aes256Ctr::from_block_cipher(aes, iv); aes.apply_keystream(&mut ciphertext); Ok(String::from_utf8(ciphertext.to_owned())?) diff --git a/matrix_sdk_crypto/src/identities/device.rs b/matrix_sdk_crypto/src/identities/device.rs index 0fef3a7a..5f782a60 100644 --- a/matrix_sdk_crypto/src/identities/device.rs +++ b/matrix_sdk_crypto/src/identities/device.rs @@ -25,15 +25,13 @@ use std::{ use atomic::Atomic; use matrix_sdk_common::locks::Mutex; use ruma::{ - api::client::r0::keys::SignedKey, - encryption::DeviceKeys, + encryption::{DeviceKeys, SignedKey}, events::{ forwarded_room_key::ForwardedRoomKeyToDeviceEventContent, - room::encrypted::EncryptedEventContent, EventType, - }, - identifiers::{ - DeviceId, DeviceIdBox, DeviceKeyAlgorithm, DeviceKeyId, EventEncryptionAlgorithm, UserId, + key::verification::VerificationMethod, room::encrypted::EncryptedEventContent, + AnyToDeviceEventContent, }, + DeviceId, DeviceIdBox, DeviceKeyAlgorithm, DeviceKeyId, EventEncryptionAlgorithm, UserId, }; use serde::{Deserialize, Deserializer, Serialize, Serializer}; use serde_json::{json, Value}; @@ -42,11 +40,11 @@ use tracing::warn; use super::{atomic_bool_deserializer, atomic_bool_serializer}; use crate::{ error::{EventError, OlmError, OlmResult, SignatureError}, - identities::{OwnUserIdentity, UserIdentities}, + identities::{ReadOnlyOwnUserIdentity, ReadOnlyUserIdentities}, olm::{InboundGroupSession, PrivateCrossSigningIdentity, Session, Utility}, store::{Changes, CryptoStore, DeviceChanges, Result as StoreResult}, verification::VerificationMachine, - OutgoingVerificationRequest, Sas, ToDeviceRequest, + OutgoingVerificationRequest, Sas, ToDeviceRequest, VerificationRequest, }; #[cfg(test)] use crate::{OlmMachine, ReadOnlyAccount}; @@ -107,8 +105,8 @@ pub struct Device { pub(crate) inner: ReadOnlyDevice, pub(crate) private_identity: Arc>, pub(crate) verification_machine: VerificationMachine, - pub(crate) own_identity: Option, - pub(crate) device_owner_identity: Option, + pub(crate) own_identity: Option, + pub(crate) device_owner_identity: Option, } impl std::fmt::Debug for Device { @@ -128,7 +126,13 @@ impl Deref for Device { impl Device { /// Start a interactive verification with this `Device` /// - /// Returns a `Sas` object and to-device request that needs to be sent out. + /// Returns a `Sas` object and a to-device request that needs to be sent + /// out. + /// + /// This method has been deprecated in the spec and the + /// [`request_verification()`] method should be used instead. + /// + /// [`request_verification()`]: #method.request_verification pub async fn start_verification(&self) -> StoreResult<(Sas, ToDeviceRequest)> { let (sas, request) = self.verification_machine.start_sas(self.inner.clone()).await?; @@ -139,6 +143,42 @@ impl Device { } } + /// Request an interacitve verification with this `Device` + /// + /// Returns a `VerificationRequest` object and a to-device request that + /// needs to be sent out. + pub async fn request_verification(&self) -> (VerificationRequest, OutgoingVerificationRequest) { + self.request_verification_helper(None).await + } + + /// Request an interacitve verification with this `Device` + /// + /// Returns a `VerificationRequest` object and a to-device request that + /// needs to be sent out. + /// + /// # Arguments + /// + /// * `methods` - The verification methods that we want to support. + pub async fn request_verification_with_methods( + &self, + methods: Vec, + ) -> (VerificationRequest, OutgoingVerificationRequest) { + self.request_verification_helper(Some(methods)).await + } + + async fn request_verification_helper( + &self, + methods: Option>, + ) -> (VerificationRequest, OutgoingVerificationRequest) { + self.verification_machine + .request_to_device_verification( + self.user_id(), + vec![self.device_id().to_owned()], + methods, + ) + .await + } + /// Get the Olm sessions that belong to this device. pub(crate) async fn get_sessions(&self) -> StoreResult>>>> { if let Some(k) = self.get_key(DeviceKeyAlgorithm::Curve25519) { @@ -148,9 +188,20 @@ impl Device { } } - /// Get the trust state of the device. - pub fn trust_state(&self) -> bool { - self.inner.trust_state(&self.own_identity, &self.device_owner_identity) + /// Is this device considered to be verified. + /// + /// This method returns true if either [`is_locally_trusted()`] returns true + /// or if [`is_cross_signing_trusted()`] returns true. + /// + /// [`is_locally_trusted()`]: #method.is_locally_trusted + /// [`is_cross_signing_trusted()`]: #method.is_cross_signing_trusted + pub fn verified(&self) -> bool { + self.inner.verified(&self.own_identity, &self.device_owner_identity) + } + + /// Is this device considered to be verified using cross signing. + pub fn is_cross_signing_trusted(&self) -> bool { + self.inner.is_cross_signing_trusted(&self.own_identity, &self.device_owner_identity) } /// Set the local trust state of the device to the given state. @@ -176,15 +227,12 @@ impl Device { /// /// # Arguments /// - /// * `event_type` - The type of the event. - /// /// * `content` - The content of the event that should be encrypted. pub(crate) async fn encrypt( &self, - event_type: EventType, - content: Value, + content: AnyToDeviceEventContent, ) -> OlmResult<(Session, EncryptedEventContent)> { - self.inner.encrypt(&*self.verification_machine.store, event_type, content).await + self.inner.encrypt(&*self.verification_machine.store, content).await } /// Encrypt the given inbound group session as a forwarded room key for this @@ -213,8 +261,7 @@ impl Device { ); }; - let content = serde_json::to_value(content)?; - self.encrypt(EventType::ForwardedRoomKey, content).await + self.encrypt(AnyToDeviceEventContent::ForwardedRoomKey(content)).await } } @@ -224,8 +271,8 @@ pub struct UserDevices { pub(crate) inner: HashMap, pub(crate) private_identity: Arc>, pub(crate) verification_machine: VerificationMachine, - pub(crate) own_identity: Option, - pub(crate) device_owner_identity: Option, + pub(crate) own_identity: Option, + pub(crate) device_owner_identity: Option, } impl UserDevices { @@ -243,7 +290,7 @@ impl UserDevices { /// Returns true if there is at least one devices of this user that is /// considered to be verified, false otherwise. pub fn is_any_verified(&self) -> bool { - self.inner.values().any(|d| d.trust_state(&self.own_identity, &self.device_owner_identity)) + self.inner.values().any(|d| d.verified(&self.own_identity, &self.device_owner_identity)) } /// Iterator over all the device ids of the user devices. @@ -347,7 +394,7 @@ impl ReadOnlyDevice { } /// Is the device locally marked as trusted. - pub fn is_trusted(&self) -> bool { + pub fn is_locally_trusted(&self) -> bool { self.local_trust_state() == LocalTrust::Verified } @@ -376,53 +423,47 @@ impl ReadOnlyDevice { self.deleted.load(Ordering::Relaxed) } - pub(crate) fn trust_state( + pub(crate) fn verified( &self, - own_identity: &Option, - device_owner: &Option, + own_identity: &Option, + device_owner: &Option, ) -> bool { - // TODO we want to return an enum mentioning if the trust is local, if - // only the identity is trusted, if the identity and the device are - // trusted. - if self.is_trusted() { - // If the device is locally marked as verified just return so, no - // need to check signatures. - true - } else { - own_identity.as_ref().map_or(false, |own_identity| { - // Our own identity needs to be marked as verified. - own_identity.is_verified() - && device_owner - .as_ref() - .map(|device_identity| match device_identity { - // If it's one of our own devices, just check that - // we signed the device. - UserIdentities::Own(_) => { - own_identity.is_device_signed(self).map_or(false, |_| true) - } + self.is_locally_trusted() || self.is_cross_signing_trusted(own_identity, device_owner) + } - // If it's a device from someone else, first check - // that our user has signed the other user and then - // check if the other user has signed this device. - UserIdentities::Other(device_identity) => { - own_identity - .is_identity_signed(device_identity) - .map_or(false, |_| true) - && device_identity - .is_device_signed(self) - .map_or(false, |_| true) - } - }) - .unwrap_or(false) - }) - } + pub(crate) fn is_cross_signing_trusted( + &self, + own_identity: &Option, + device_owner: &Option, + ) -> bool { + own_identity.as_ref().map_or(false, |own_identity| { + // Our own identity needs to be marked as verified. + own_identity.is_verified() + && device_owner + .as_ref() + .map(|device_identity| match device_identity { + // If it's one of our own devices, just check that + // we signed the device. + ReadOnlyUserIdentities::Own(_) => { + own_identity.is_device_signed(self).map_or(false, |_| true) + } + + // If it's a device from someone else, first check + // that our user has signed the other user and then + // check if the other user has signed this device. + ReadOnlyUserIdentities::Other(device_identity) => { + own_identity.is_identity_signed(device_identity).map_or(false, |_| true) + && device_identity.is_device_signed(self).map_or(false, |_| true) + } + }) + .unwrap_or(false) + }) } pub(crate) async fn encrypt( &self, store: &dyn CryptoStore, - event_type: EventType, - content: Value, + content: AnyToDeviceEventContent, ) -> OlmResult<(Session, EncryptedEventContent)> { let sender_key = if let Some(k) = self.get_key(DeviceKeyAlgorithm::Curve25519) { k @@ -455,7 +496,7 @@ impl ReadOnlyDevice { return Err(OlmError::MissingSession); }; - let message = session.encrypt(self, event_type, content).await?; + let message = session.encrypt(self, content).await?; Ok((session, message)) } diff --git a/matrix_sdk_crypto/src/identities/manager.rs b/matrix_sdk_crypto/src/identities/manager.rs index 0af468b7..58763126 100644 --- a/matrix_sdk_crypto/src/identities/manager.rs +++ b/matrix_sdk_crypto/src/identities/manager.rs @@ -21,17 +21,16 @@ use std::{ use futures::future::join_all; use matrix_sdk_common::executor::spawn; use ruma::{ - api::client::r0::keys::get_keys::Response as KeysQueryResponse, - encryption::DeviceKeys, - identifiers::{DeviceId, DeviceIdBox, UserId}, + api::client::r0::keys::get_keys::Response as KeysQueryResponse, encryption::DeviceKeys, + DeviceId, DeviceIdBox, UserId, }; use tracing::{trace, warn}; use crate::{ error::OlmResult, identities::{ - MasterPubkey, OwnUserIdentity, ReadOnlyDevice, SelfSigningPubkey, UserIdentities, - UserIdentity, UserSigningPubkey, + MasterPubkey, ReadOnlyDevice, ReadOnlyOwnUserIdentity, ReadOnlyUserIdentities, + ReadOnlyUserIdentity, SelfSigningPubkey, UserSigningPubkey, }, requests::KeysQueryRequest, store::{Changes, DeviceChanges, IdentityChanges, Result as StoreResult, Store}, @@ -247,7 +246,7 @@ impl IdentityManager { let result = if let Some(mut i) = self.store.get_user_identity(user_id).await? { match &mut i { - UserIdentities::Own(ref mut identity) => { + ReadOnlyUserIdentities::Own(ref mut identity) => { let user_signing = if let Some(s) = response.user_signing_keys.get(user_id) { UserSigningPubkey::from(s) @@ -262,7 +261,7 @@ impl IdentityManager { identity.update(master_key, self_signing, user_signing).map(|_| (i, false)) } - UserIdentities::Other(ref mut identity) => { + ReadOnlyUserIdentities::Other(ref mut identity) => { identity.update(master_key, self_signing).map(|_| (i, false)) } } @@ -281,8 +280,8 @@ impl IdentityManager { continue; } - OwnUserIdentity::new(master_key, self_signing, user_signing) - .map(|i| (UserIdentities::Own(i), true)) + ReadOnlyOwnUserIdentity::new(master_key, self_signing, user_signing) + .map(|i| (ReadOnlyUserIdentities::Own(i), true)) } else { warn!( "User identity for our own user {} didn't contain a \ @@ -295,8 +294,8 @@ impl IdentityManager { warn!("User id mismatch in one of the cross signing keys for user {}", user_id); continue; } else { - UserIdentity::new(master_key, self_signing) - .map(|i| (UserIdentities::Other(i), true)) + ReadOnlyUserIdentity::new(master_key, self_signing) + .map(|i| (ReadOnlyUserIdentities::Other(i), true)) }; match result { diff --git a/matrix_sdk_crypto/src/identities/mod.rs b/matrix_sdk_crypto/src/identities/mod.rs index 873cecfa..d90206b3 100644 --- a/matrix_sdk_crypto/src/identities/mod.rs +++ b/matrix_sdk_crypto/src/identities/mod.rs @@ -53,8 +53,8 @@ pub use device::{Device, LocalTrust, ReadOnlyDevice, UserDevices}; pub(crate) use manager::IdentityManager; use serde::{Deserialize, Deserializer, Serializer}; pub use user::{ - MasterPubkey, OwnUserIdentity, SelfSigningPubkey, UserIdentities, UserIdentity, - UserSigningPubkey, + MasterPubkey, OwnUserIdentity, ReadOnlyOwnUserIdentity, ReadOnlyUserIdentities, + ReadOnlyUserIdentity, SelfSigningPubkey, UserIdentities, UserIdentity, UserSigningPubkey, }; // These methods are only here because Serialize and Deserialize don't seem to diff --git a/matrix_sdk_crypto/src/identities/user.rs b/matrix_sdk_crypto/src/identities/user.rs index 4142ea0f..178aae54 100644 --- a/matrix_sdk_crypto/src/identities/user.rs +++ b/matrix_sdk_crypto/src/identities/user.rs @@ -15,6 +15,7 @@ use std::{ collections::{btree_map::Iter, BTreeMap}, convert::TryFrom, + ops::Deref, sync::{ atomic::{AtomicBool, Ordering}, Arc, @@ -22,8 +23,11 @@ use std::{ }; use ruma::{ - api::client::r0::keys::{CrossSigningKey, KeyUsage}, - DeviceKeyId, UserId, + encryption::{CrossSigningKey, KeyUsage}, + events::{ + key::verification::VerificationMethod, room::message::KeyVerificationRequestEventContent, + }, + DeviceIdBox, DeviceKeyId, EventId, RoomId, UserId, }; use serde::{Deserialize, Serialize}; use serde_json::to_value; @@ -31,7 +35,173 @@ use serde_json::to_value; use super::{atomic_bool_deserializer, atomic_bool_serializer}; #[cfg(test)] use crate::olm::PrivateCrossSigningIdentity; -use crate::{error::SignatureError, olm::Utility, ReadOnlyDevice}; +use crate::{ + error::SignatureError, olm::Utility, verification::VerificationMachine, CryptoStoreError, + OutgoingVerificationRequest, ReadOnlyDevice, VerificationRequest, +}; + +/// Enum over the different user identity types we can have. +#[derive(Debug, Clone)] +pub enum UserIdentities { + /// Our own user identity. + Own(OwnUserIdentity), + /// An identity belonging to another user. + Other(UserIdentity), +} + +impl UserIdentities { + /// Destructure the enum into an `OwnUserIdentity` if it's of the correct + /// type. + pub fn own(self) -> Option { + match self { + Self::Own(i) => Some(i), + _ => None, + } + } + + /// Destructure the enum into an `UserIdentity` if it's of the correct + /// type. + pub fn other(self) -> Option { + match self { + Self::Other(i) => Some(i), + _ => None, + } + } +} + +impl From for UserIdentities { + fn from(i: OwnUserIdentity) -> Self { + Self::Own(i) + } +} + +impl From for UserIdentities { + fn from(i: UserIdentity) -> Self { + Self::Other(i) + } +} + +/// Struct representing a cross signing identity of a user. +/// +/// This is the user identity of a user that isn't our own. Other users will +/// only contain a master key and a self signing key, meaning that only device +/// signatures can be checked with this identity. +/// +/// This struct wraps a read-only version of the struct and allows verifications +/// to be requested to verify our own device with the user identity. +#[derive(Debug, Clone)] +pub struct OwnUserIdentity { + pub(crate) inner: ReadOnlyOwnUserIdentity, + pub(crate) verification_machine: VerificationMachine, +} + +impl Deref for OwnUserIdentity { + type Target = ReadOnlyOwnUserIdentity; + + fn deref(&self) -> &Self::Target { + &self.inner + } +} + +impl OwnUserIdentity { + /// Send a verification request to our other devices. + pub async fn request_verification( + &self, + ) -> Result<(VerificationRequest, OutgoingVerificationRequest), CryptoStoreError> { + self.request_verification_helper(None).await + } + + /// Send a verification request to our other devices while specifying our + /// supported methods. + /// + /// # Arguments + /// + /// * `methods` - The verification methods that we're supporting. + pub async fn request_verification_with_methods( + &self, + methods: Vec, + ) -> Result<(VerificationRequest, OutgoingVerificationRequest), CryptoStoreError> { + self.request_verification_helper(Some(methods)).await + } + + async fn request_verification_helper( + &self, + methods: Option>, + ) -> Result<(VerificationRequest, OutgoingVerificationRequest), CryptoStoreError> { + let devices: Vec = self + .verification_machine + .store + .get_user_devices(self.user_id()) + .await? + .into_iter() + .map(|(d, _)| d) + .filter(|d| &**d != self.verification_machine.own_device_id()) + .collect(); + + Ok(self + .verification_machine + .request_to_device_verification(self.user_id(), devices, methods) + .await) + } +} + +/// Struct representing a cross signing identity of a user. +/// +/// This is the user identity of a user that isn't our own. Other users will +/// only contain a master key and a self signing key, meaning that only device +/// signatures can be checked with this identity. +/// +/// This struct wraps a read-only version of the struct and allows verifications +/// to be requested to verify our own device with the user identity. +#[derive(Debug, Clone)] +pub struct UserIdentity { + pub(crate) inner: ReadOnlyUserIdentity, + pub(crate) verification_machine: VerificationMachine, +} + +impl Deref for UserIdentity { + type Target = ReadOnlyUserIdentity; + + fn deref(&self) -> &Self::Target { + &self.inner + } +} + +impl UserIdentity { + /// Create a `VerificationRequest` object after the verification request + /// content has been sent out. + pub async fn request_verification( + &self, + room_id: &RoomId, + request_event_id: &EventId, + methods: Option>, + ) -> VerificationRequest { + self.verification_machine + .request_verification(&self.inner, room_id, request_event_id, methods) + .await + } + + /// Send a verification request to the given user. + /// + /// The returned content needs to be sent out into a DM room with the given + /// user. + /// + /// After the content has been sent out a `VerificationRequest` can be + /// started with the [`request_verification()`] method. + /// + /// [`request_verification()`]: #method.request_verification + pub async fn verification_request_content( + &self, + methods: Option>, + ) -> KeyVerificationRequestEventContent { + VerificationRequest::request( + self.verification_machine.own_user_id(), + self.verification_machine.own_device_id(), + self.user_id(), + methods, + ) + } +} /// Wrapper for a cross signing key marking it as the master key. /// @@ -213,6 +383,14 @@ impl MasterPubkey { self.0.keys.get(key_id.as_str()).map(|k| k.as_str()) } + /// Get the first available master key. + /// + /// There's usually only a single master key so this will usually fetch the + /// only key. + pub fn get_first_key(&self) -> Option<&str> { + self.0.keys.values().map(|k| k.as_str()).next() + } + /// Check if the given cross signing sub-key is signed by the master key. /// /// # Arguments @@ -350,47 +528,47 @@ impl<'a> IntoIterator for &'a SelfSigningPubkey { /// Enum over the different user identity types we can have. #[derive(Debug, Clone, Serialize, Deserialize)] -pub enum UserIdentities { +pub enum ReadOnlyUserIdentities { /// Our own user identity. - Own(OwnUserIdentity), + Own(ReadOnlyOwnUserIdentity), /// Identities of other users. - Other(UserIdentity), + Other(ReadOnlyUserIdentity), } -impl From for UserIdentities { - fn from(identity: OwnUserIdentity) -> Self { - UserIdentities::Own(identity) +impl From for ReadOnlyUserIdentities { + fn from(identity: ReadOnlyOwnUserIdentity) -> Self { + ReadOnlyUserIdentities::Own(identity) } } -impl From for UserIdentities { - fn from(identity: UserIdentity) -> Self { - UserIdentities::Other(identity) +impl From for ReadOnlyUserIdentities { + fn from(identity: ReadOnlyUserIdentity) -> Self { + ReadOnlyUserIdentities::Other(identity) } } -impl UserIdentities { +impl ReadOnlyUserIdentities { /// The unique user id of this identity. pub fn user_id(&self) -> &UserId { match self { - UserIdentities::Own(i) => i.user_id(), - UserIdentities::Other(i) => i.user_id(), + ReadOnlyUserIdentities::Own(i) => i.user_id(), + ReadOnlyUserIdentities::Other(i) => i.user_id(), } } /// Get the master key of the identity. pub fn master_key(&self) -> &MasterPubkey { match self { - UserIdentities::Own(i) => i.master_key(), - UserIdentities::Other(i) => i.master_key(), + ReadOnlyUserIdentities::Own(i) => i.master_key(), + ReadOnlyUserIdentities::Other(i) => i.master_key(), } } /// Get the self-signing key of the identity. pub fn self_signing_key(&self) -> &SelfSigningPubkey { match self { - UserIdentities::Own(i) => &i.self_signing_key, - UserIdentities::Other(i) => &i.self_signing_key, + ReadOnlyUserIdentities::Own(i) => &i.self_signing_key, + ReadOnlyUserIdentities::Other(i) => &i.self_signing_key, } } @@ -398,32 +576,32 @@ impl UserIdentities { /// own user identity.. pub fn user_signing_key(&self) -> Option<&UserSigningPubkey> { match self { - UserIdentities::Own(i) => Some(&i.user_signing_key), - UserIdentities::Other(_) => None, + ReadOnlyUserIdentities::Own(i) => Some(&i.user_signing_key), + ReadOnlyUserIdentities::Other(_) => None, } } - /// Destructure the enum into an `OwnUserIdentity` if it's of the correct - /// type. - pub fn own(&self) -> Option<&OwnUserIdentity> { + /// Destructure the enum into an `ReadOnlyOwnUserIdentity` if it's of the + /// correct type. + pub fn own(&self) -> Option<&ReadOnlyOwnUserIdentity> { match self { - UserIdentities::Own(i) => Some(i), + ReadOnlyUserIdentities::Own(i) => Some(i), _ => None, } } /// Destructure the enum into an `UserIdentity` if it's of the correct /// type. - pub fn other(&self) -> Option<&UserIdentity> { + pub fn other(&self) -> Option<&ReadOnlyUserIdentity> { match self { - UserIdentities::Other(i) => Some(i), + ReadOnlyUserIdentities::Other(i) => Some(i), _ => None, } } } -impl PartialEq for UserIdentities { - fn eq(&self, other: &UserIdentities) -> bool { +impl PartialEq for ReadOnlyUserIdentities { + fn eq(&self, other: &ReadOnlyUserIdentities) -> bool { self.user_id() == other.user_id() } } @@ -434,13 +612,13 @@ impl PartialEq for UserIdentities { /// only contain a master key and a self signing key, meaning that only device /// signatures can be checked with this identity. #[derive(Debug, Clone, Deserialize, Serialize)] -pub struct UserIdentity { +pub struct ReadOnlyUserIdentity { user_id: Arc, pub(crate) master_key: MasterPubkey, self_signing_key: SelfSigningPubkey, } -impl UserIdentity { +impl ReadOnlyUserIdentity { /// Create a new user identity with the given master and self signing key. /// /// # Arguments @@ -535,7 +713,7 @@ impl UserIdentity { /// This identity can verify other identities as well as devices belonging to /// the identity. #[derive(Debug, Clone, Serialize, Deserialize)] -pub struct OwnUserIdentity { +pub struct ReadOnlyOwnUserIdentity { user_id: Arc, master_key: MasterPubkey, self_signing_key: SelfSigningPubkey, @@ -547,7 +725,7 @@ pub struct OwnUserIdentity { verified: Arc, } -impl OwnUserIdentity { +impl ReadOnlyOwnUserIdentity { /// Create a new own user identity with the given master, self signing, and /// user signing key. /// @@ -607,7 +785,10 @@ impl OwnUserIdentity { /// /// Returns an empty result if the signature check succeeded, otherwise a /// SignatureError indicating why the check failed. - pub fn is_identity_signed(&self, identity: &UserIdentity) -> Result<(), SignatureError> { + pub fn is_identity_signed( + &self, + identity: &ReadOnlyUserIdentity, + ) -> Result<(), SignatureError> { self.user_signing_key.verify_master_key(&identity.master_key) } @@ -684,7 +865,7 @@ pub(crate) mod test { use matrix_sdk_test::async_test; use ruma::{api::client::r0::keys::get_keys::Response as KeyQueryResponse, user_id}; - use super::{OwnUserIdentity, UserIdentities, UserIdentity}; + use super::{ReadOnlyOwnUserIdentity, ReadOnlyUserIdentities, ReadOnlyUserIdentity}; use crate::{ identities::{ manager::test::{other_key_query, own_key_query}, @@ -702,28 +883,29 @@ pub(crate) mod test { (first, second) } - fn own_identity(response: &KeyQueryResponse) -> OwnUserIdentity { + fn own_identity(response: &KeyQueryResponse) -> ReadOnlyOwnUserIdentity { let user_id = user_id!("@example:localhost"); let master_key = response.master_keys.get(&user_id).unwrap(); let user_signing = response.user_signing_keys.get(&user_id).unwrap(); let self_signing = response.self_signing_keys.get(&user_id).unwrap(); - OwnUserIdentity::new(master_key.into(), self_signing.into(), user_signing.into()).unwrap() + ReadOnlyOwnUserIdentity::new(master_key.into(), self_signing.into(), user_signing.into()) + .unwrap() } - pub(crate) fn get_own_identity() -> OwnUserIdentity { + pub(crate) fn get_own_identity() -> ReadOnlyOwnUserIdentity { own_identity(&own_key_query()) } - pub(crate) fn get_other_identity() -> UserIdentity { + pub(crate) fn get_other_identity() -> ReadOnlyUserIdentity { let user_id = user_id!("@example2:localhost"); let response = other_key_query(); let master_key = response.master_keys.get(&user_id).unwrap(); let self_signing = response.self_signing_keys.get(&user_id).unwrap(); - UserIdentity::new(master_key.into(), self_signing.into()).unwrap() + ReadOnlyUserIdentity::new(master_key.into(), self_signing.into()).unwrap() } #[test] @@ -735,7 +917,8 @@ pub(crate) mod test { let user_signing = response.user_signing_keys.get(&user_id).unwrap(); let self_signing = response.self_signing_keys.get(&user_id).unwrap(); - OwnUserIdentity::new(master_key.into(), self_signing.into(), user_signing.into()).unwrap(); + ReadOnlyOwnUserIdentity::new(master_key.into(), self_signing.into(), user_signing.into()) + .unwrap(); } #[test] @@ -765,7 +948,7 @@ pub(crate) mod test { verification_machine: verification_machine.clone(), private_identity: private_identity.clone(), own_identity: Some(identity.clone()), - device_owner_identity: Some(UserIdentities::Own(identity.clone())), + device_owner_identity: Some(ReadOnlyUserIdentities::Own(identity.clone())), }; let second = Device { @@ -773,18 +956,18 @@ pub(crate) mod test { verification_machine, private_identity, own_identity: Some(identity.clone()), - device_owner_identity: Some(UserIdentities::Own(identity.clone())), + device_owner_identity: Some(ReadOnlyUserIdentities::Own(identity.clone())), }; - assert!(!second.trust_state()); - assert!(!second.is_trusted()); + assert!(!second.is_locally_trusted()); + assert!(!second.is_cross_signing_trusted()); - assert!(!first.trust_state()); - assert!(!first.is_trusted()); + assert!(!first.is_locally_trusted()); + assert!(!first.is_cross_signing_trusted()); identity.mark_as_verified(); - assert!(second.trust_state()); - assert!(!first.trust_state()); + assert!(second.verified()); + assert!(!first.verified()); } #[async_test] @@ -803,7 +986,7 @@ pub(crate) mod test { Arc::new(MemoryStore::new()), ); - let public_identity = identity.as_public_identity().await.unwrap(); + let public_identity = identity.to_public_identity().await.unwrap(); let mut device = Device { inner: device, @@ -813,12 +996,12 @@ pub(crate) mod test { device_owner_identity: Some(public_identity.clone().into()), }; - assert!(!device.trust_state()); + assert!(!device.verified()); let mut device_keys = device.as_device_keys(); identity.sign_device_keys(&mut device_keys).await.unwrap(); device.inner.signatures = Arc::new(device_keys.signatures); - assert!(device.trust_state()); + assert!(device.verified()); } } diff --git a/matrix_sdk_crypto/src/key_request.rs b/matrix_sdk_crypto/src/key_request.rs index 9732d8d4..570837d0 100644 --- a/matrix_sdk_crypto/src/key_request.rs +++ b/matrix_sdk_crypto/src/key_request.rs @@ -20,21 +20,20 @@ // If we don't trust the device store an object that remembers the request and // let the users introspect that object. -use std::{collections::BTreeMap, sync::Arc}; +use std::sync::Arc; use dashmap::{mapref::entry::Entry, DashMap, DashSet}; use matrix_sdk_common::uuid::Uuid; use ruma::{ - api::client::r0::to_device::DeviceIdOrAllDevices, events::{ forwarded_room_key::ForwardedRoomKeyToDeviceEventContent, room_key_request::{Action, RequestedKeyInfo, RoomKeyRequestToDeviceEventContent}, - AnyToDeviceEvent, EventType, ToDeviceEvent, + AnyToDeviceEvent, AnyToDeviceEventContent, ToDeviceEvent, }, - identifiers::{DeviceId, DeviceIdBox, EventEncryptionAlgorithm, RoomId, UserId}, + to_device::DeviceIdOrAllDevices, + DeviceId, DeviceIdBox, EventEncryptionAlgorithm, RoomId, UserId, }; use serde::{Deserialize, Serialize}; -use serde_json::value::to_raw_value; use thiserror::Error; use tracing::{error, info, trace, warn}; @@ -150,7 +149,7 @@ pub struct OutgoingKeyRequest { } impl OutgoingKeyRequest { - fn to_request(&self, own_device_id: &DeviceId) -> Result { + fn to_request(&self, own_device_id: &DeviceId) -> OutgoingRequest { let content = RoomKeyRequestToDeviceEventContent::new( Action::Request, Some(self.info.clone()), @@ -158,13 +157,17 @@ impl OutgoingKeyRequest { self.request_id.to_string(), ); - wrap_key_request_content(self.request_recipient.clone(), self.request_id, &content) + let request = ToDeviceRequest::new_with_id( + &self.request_recipient, + DeviceIdOrAllDevices::AllDevices, + AnyToDeviceEventContent::RoomKeyRequest(content), + self.request_id, + ); + + OutgoingRequest { request_id: request.txn_id, request: Arc::new(request.into()) } } - fn to_cancellation( - &self, - own_device_id: &DeviceId, - ) -> Result { + fn to_cancellation(&self, own_device_id: &DeviceId) -> OutgoingRequest { let content = RoomKeyRequestToDeviceEventContent::new( Action::CancelRequest, None, @@ -172,8 +175,13 @@ impl OutgoingKeyRequest { self.request_id.to_string(), ); - let id = Uuid::new_v4(); - wrap_key_request_content(self.request_recipient.clone(), id, &content) + let request = ToDeviceRequest::new( + &self.request_recipient, + DeviceIdOrAllDevices::AllDevices, + AnyToDeviceEventContent::RoomKeyRequest(content), + ); + + OutgoingRequest { request_id: request.txn_id, request: Arc::new(request.into()) } } } @@ -187,26 +195,6 @@ impl PartialEq for OutgoingKeyRequest { } } -fn wrap_key_request_content( - recipient: UserId, - id: Uuid, - content: &RoomKeyRequestToDeviceEventContent, -) -> Result { - let mut messages = BTreeMap::new(); - - messages - .entry(recipient) - .or_insert_with(BTreeMap::new) - .insert(DeviceIdOrAllDevices::AllDevices, to_raw_value(content)?); - - Ok(OutgoingRequest { - request_id: id, - request: Arc::new( - ToDeviceRequest { event_type: EventType::RoomKeyRequest, txn_id: id, messages }.into(), - ), - }) -} - impl KeyRequestMachine { pub fn new( user_id: Arc, @@ -229,13 +217,14 @@ impl KeyRequestMachine { /// Load stored outgoing requests that were not yet sent out. async fn load_outgoing_requests(&self) -> Result, CryptoStoreError> { - self.store + Ok(self + .store .get_unsent_key_requests() .await? .into_iter() .filter(|i| !i.sent_out) - .map(|info| info.to_request(self.device_id()).map_err(CryptoStoreError::from)) - .collect() + .map(|info| info.to_request(self.device_id())) + .collect()) } /// Our own user id. @@ -448,23 +437,15 @@ impl KeyRequestMachine { let (used_session, content) = device.encrypt_session(session.clone(), message_index).await?; - let id = Uuid::new_v4(); - let mut messages = BTreeMap::new(); - - messages.entry(device.user_id().to_owned()).or_insert_with(BTreeMap::new).insert( - DeviceIdOrAllDevices::DeviceId(device.device_id().into()), - to_raw_value(&content)?, + let request = ToDeviceRequest::new( + device.user_id(), + device.device_id().to_owned(), + AnyToDeviceEventContent::RoomEncrypted(content), ); - let request = OutgoingRequest { - request_id: id, - request: Arc::new( - ToDeviceRequest { event_type: EventType::RoomEncrypted, txn_id: id, messages } - .into(), - ), - }; - - self.outgoing_to_device_requests.insert(id, request); + let request = + OutgoingRequest { request_id: request.txn_id, request: Arc::new(request.into()) }; + self.outgoing_to_device_requests.insert(request.request_id, request); Ok(used_session) } @@ -498,7 +479,7 @@ impl KeyRequestMachine { .flatten(); let own_device_check = || { - if device.trust_state() { + if device.verified() { Ok(None) } else { Err(KeyshareDecision::UntrustedDevice) @@ -594,8 +575,8 @@ impl KeyRequestMachine { let request = self.store.get_key_request_by_info(&key_info).await?; if let Some(request) = request { - let cancel = request.to_cancellation(self.device_id())?; - let request = request.to_request(self.device_id())?; + let cancel = request.to_cancellation(self.device_id()); + let request = request.to_request(self.device_id()); Ok((Some(cancel), request)) } else { @@ -618,7 +599,7 @@ impl KeyRequestMachine { sent_out: false, }; - let outgoing_request = request.to_request(self.device_id())?; + let outgoing_request = request.to_request(self.device_id()); self.save_outgoing_key_info(request).await?; Ok(outgoing_request) @@ -717,7 +698,7 @@ impl KeyRequestMachine { // can delete it in one transaction. self.delete_key_info(&key_info).await?; - let request = key_info.to_cancellation(self.device_id())?; + let request = key_info.to_cancellation(self.device_id()); self.outgoing_to_device_requests.insert(request.request_id, request); Ok(()) @@ -789,13 +770,14 @@ mod test { use matrix_sdk_common::locks::Mutex; use matrix_sdk_test::async_test; use ruma::{ - api::client::r0::to_device::DeviceIdOrAllDevices, events::{ forwarded_room_key::ForwardedRoomKeyToDeviceEventContent, room::encrypted::EncryptedEventContent, room_key_request::RoomKeyRequestToDeviceEventContent, AnyToDeviceEvent, ToDeviceEvent, }, - room_id, user_id, DeviceIdBox, RoomId, UserId, + room_id, + to_device::DeviceIdOrAllDevices, + user_id, DeviceIdBox, RoomId, UserId, }; use super::{KeyRequestMachine, KeyshareDecision}; @@ -1203,8 +1185,7 @@ mod test { .unwrap() .get(&DeviceIdOrAllDevices::AllDevices) .unwrap(); - let content: RoomKeyRequestToDeviceEventContent = - serde_json::from_str(content.get()).unwrap(); + let content: RoomKeyRequestToDeviceEventContent = content.deserialize_as().unwrap(); alice_machine.mark_outgoing_request_as_sent(id).await.unwrap(); @@ -1233,7 +1214,7 @@ mod test { .unwrap() .get(&DeviceIdOrAllDevices::DeviceId(alice_device_id())) .unwrap(); - let content: EncryptedEventContent = serde_json::from_str(content.get()).unwrap(); + let content: EncryptedEventContent = content.deserialize_as().unwrap(); bob_machine.mark_outgoing_request_as_sent(id).await.unwrap(); @@ -1336,8 +1317,7 @@ mod test { .unwrap() .get(&DeviceIdOrAllDevices::AllDevices) .unwrap(); - let content: RoomKeyRequestToDeviceEventContent = - serde_json::from_str(content.get()).unwrap(); + let content: RoomKeyRequestToDeviceEventContent = content.deserialize_as().unwrap(); alice_machine.mark_outgoing_request_as_sent(id).await.unwrap(); @@ -1382,7 +1362,7 @@ mod test { .unwrap() .get(&DeviceIdOrAllDevices::DeviceId(alice_device_id())) .unwrap(); - let content: EncryptedEventContent = serde_json::from_str(content.get()).unwrap(); + let content: EncryptedEventContent = content.deserialize_as().unwrap(); bob_machine.mark_outgoing_request_as_sent(id).await.unwrap(); diff --git a/matrix_sdk_crypto/src/lib.rs b/matrix_sdk_crypto/src/lib.rs index 1fef664e..37251fda 100644 --- a/matrix_sdk_crypto/src/lib.rs +++ b/matrix_sdk_crypto/src/lib.rs @@ -45,9 +45,11 @@ pub use file_encryption::{ DecryptorError, EncryptionInfo, KeyExportError, }; pub use identities::{ - Device, LocalTrust, OwnUserIdentity, ReadOnlyDevice, UserDevices, UserIdentities, UserIdentity, + Device, LocalTrust, OwnUserIdentity, ReadOnlyDevice, ReadOnlyOwnUserIdentity, + ReadOnlyUserIdentities, ReadOnlyUserIdentity, UserDevices, UserIdentities, UserIdentity, }; pub use machine::OlmMachine; +pub use matrix_qrcode; pub use olm::EncryptionSettings; pub(crate) use olm::ReadOnlyAccount; pub use requests::{ @@ -55,4 +57,6 @@ pub use requests::{ OutgoingVerificationRequest, RoomMessageRequest, ToDeviceRequest, }; pub use store::CryptoStoreError; -pub use verification::{AcceptSettings, Sas, VerificationRequest}; +pub use verification::{ + AcceptSettings, CancelInfo, QrVerification, Sas, Verification, VerificationRequest, +}; diff --git a/matrix_sdk_crypto/src/machine.rs b/matrix_sdk_crypto/src/machine.rs index 807d13cf..1fe30a6b 100644 --- a/matrix_sdk_crypto/src/machine.rs +++ b/matrix_sdk_crypto/src/machine.rs @@ -46,7 +46,7 @@ use tracing::{debug, error, info, trace, warn}; use crate::store::sled::SledStore; use crate::{ error::{EventError, MegolmError, MegolmResult, OlmError, OlmResult}, - identities::{Device, IdentityManager, UserDevices}, + identities::{user::UserIdentities, Device, IdentityManager, UserDevices}, key_request::KeyRequestMachine, olm::{ Account, EncryptionSettings, ExportedRoomKey, GroupSessionKey, IdentityKeys, @@ -59,7 +59,7 @@ use crate::{ Changes, CryptoStore, DeviceChanges, IdentityChanges, MemoryStore, Result as StoreResult, Store, }, - verification::{Sas, VerificationMachine, VerificationRequest}, + verification::{Verification, VerificationMachine, VerificationRequest}, ToDeviceRequest, }; @@ -379,7 +379,7 @@ impl OlmMachine { *identity = id; - let public = identity.as_public_identity().await.expect( + let public = identity.to_public_identity().await.expect( "Couldn't create a public version of the identity from a new private identity", ); @@ -717,21 +717,27 @@ impl OlmMachine { Ok(()) } - /// Get a `Sas` verification object with the given flow id. - pub fn get_verification(&self, flow_id: &str) -> Option { - self.verification_machine.get_sas(flow_id) + /// Get a verification object for the given user id with the given flow id. + pub fn get_verification(&self, user_id: &UserId, flow_id: &str) -> Option { + self.verification_machine.get_verification(user_id, flow_id) } /// Get a verification request object with the given flow id. pub fn get_verification_request( &self, + user_id: &UserId, flow_id: impl AsRef, ) -> Option { - self.verification_machine.get_request(flow_id) + self.verification_machine.get_request(user_id, flow_id) } - async fn update_one_time_key_count(&self, key_count: &BTreeMap) { - self.account.update_uploaded_key_count(key_count).await; + /// Get all the verification requests of a given user. + pub fn get_verification_requests(&self, user_id: &UserId) -> Vec { + self.verification_machine.get_requests(user_id) + } + + fn update_one_time_key_count(&self, key_count: &BTreeMap) { + self.account.update_uploaded_key_count(key_count); } async fn handle_to_device_event(&self, event: &AnyToDeviceEvent) { @@ -749,11 +755,11 @@ impl OlmMachine { | AnyToDeviceEvent::KeyVerificationStart(..) => { self.handle_verification_event(event).await; } - AnyToDeviceEvent::Dummy(_) => {} - AnyToDeviceEvent::RoomKey(_) => {} - AnyToDeviceEvent::ForwardedRoomKey(_) => {} - AnyToDeviceEvent::RoomEncrypted(_) => {} - AnyToDeviceEvent::Custom(_) => {} + AnyToDeviceEvent::Dummy(_) + | AnyToDeviceEvent::RoomKey(_) + | AnyToDeviceEvent::ForwardedRoomKey(_) + | AnyToDeviceEvent::RoomEncrypted(_) => {} + _ => {} } } @@ -783,14 +789,14 @@ impl OlmMachine { one_time_keys_counts: &BTreeMap, ) -> OlmResult { // Remove verification objects that have expired or are done. - self.verification_machine.garbage_collect(); + let mut events = self.verification_machine.garbage_collect(); // Always save the account, a new session might get created which also // touches the account. let mut changes = Changes { account: Some(self.account.inner.clone()), ..Default::default() }; - self.update_one_time_key_count(one_time_keys_counts).await; + self.update_one_time_key_count(one_time_keys_counts); for user_id in &changed_devices.changed { if let Err(e) = self.identity_manager.mark_user_as_changed(user_id).await { @@ -798,8 +804,6 @@ impl OlmMachine { } } - let mut events = Vec::new(); - for mut raw_event in to_device_events.events { let event = match raw_event.deserialize() { Ok(e) => e, @@ -922,7 +926,7 @@ impl OlmMachine { .unwrap_or(false) }) { if (self.user_id() == device.user_id() && self.device_id() == device.device_id()) - || device.is_trusted() + || device.verified() { VerificationState::Trusted } else { @@ -1048,6 +1052,18 @@ impl OlmMachine { self.store.get_device(user_id, device_id).await } + /// Get the cross signing user identity of a user. + /// + /// # Arguments + /// + /// * `user_id` - The unique id of the user that the identity belongs to + /// + /// Returns a `UserIdentities` enum if one is found and the crypto store + /// didn't throw an error. + pub async fn get_identity(&self, user_id: &UserId) -> StoreResult> { + self.store.get_identity(user_id).await + } + /// Get a map holding all the devices of an user. /// /// # Arguments @@ -1225,22 +1241,22 @@ pub(crate) mod test { use matrix_sdk_test::test_json; use ruma::{ api::{ - client::r0::keys::{claim_keys, get_keys, upload_keys, OneTimeKey}, + client::r0::keys::{claim_keys, get_keys, upload_keys}, IncomingResponse, }, + encryption::OneTimeKey, + event_id, events::{ + dummy::DummyToDeviceEventContent, room::{ encrypted::EncryptedEventContent, message::{MessageEventContent, MessageType}, }, AnyMessageEventContent, AnySyncMessageEvent, AnySyncRoomEvent, AnyToDeviceEvent, - EventType, SyncMessageEvent, ToDeviceEvent, Unsigned, + AnyToDeviceEventContent, SyncMessageEvent, ToDeviceEvent, Unsigned, }, - identifiers::{ - event_id, room_id, user_id, DeviceId, DeviceKeyAlgorithm, DeviceKeyId, UserId, - }, - serde::Raw, - uint, MilliSecondsSinceUnixEpoch, + room_id, uint, user_id, DeviceId, DeviceKeyAlgorithm, DeviceKeyId, + MilliSecondsSinceUnixEpoch, UserId, }; use serde_json::json; @@ -1285,12 +1301,16 @@ pub(crate) mod test { fn to_device_requests_to_content(requests: Vec>) -> EncryptedEventContent { let to_device_request = &requests[0]; - let content: Raw = serde_json::from_str( - to_device_request.messages.values().next().unwrap().values().next().unwrap().get(), - ) - .unwrap(); - - content.deserialize().unwrap() + to_device_request + .messages + .values() + .next() + .unwrap() + .values() + .next() + .unwrap() + .deserialize_as() + .unwrap() } pub(crate) async fn get_prepared_machine() -> (OlmMachine, OneTimeKeys) { @@ -1352,7 +1372,10 @@ pub(crate) mod test { let bob_device = alice.get_device(&bob.user_id, &bob.device_id).await.unwrap().unwrap(); - let (session, content) = bob_device.encrypt(EventType::Dummy, json!({})).await.unwrap(); + let (session, content) = bob_device + .encrypt(AnyToDeviceEventContent::Dummy(DummyToDeviceEventContent::new())) + .await + .unwrap(); alice.store.save_sessions(&[session]).await.unwrap(); let event = ToDeviceEvent { sender: alice.user_id().clone(), content }; @@ -1387,6 +1410,10 @@ pub(crate) mod test { response.one_time_key_counts.insert(DeviceKeyAlgorithm::SignedCurve25519, uint!(50)); machine.receive_keys_upload_response(&response).await.unwrap(); assert!(!machine.should_upload_keys().await); + + response.one_time_key_counts.remove(&DeviceKeyAlgorithm::SignedCurve25519); + machine.receive_keys_upload_response(&response).await.unwrap(); + assert!(!machine.should_upload_keys().await); } #[tokio::test] @@ -1587,7 +1614,11 @@ pub(crate) mod test { let event = ToDeviceEvent { sender: alice.user_id().clone(), - content: bob_device.encrypt(EventType::Dummy, json!({})).await.unwrap().1, + content: bob_device + .encrypt(AnyToDeviceEventContent::Dummy(DummyToDeviceEventContent::new())) + .await + .unwrap() + .1, }; let event = bob.decrypt_to_device_event(&event).await.unwrap().event.deserialize().unwrap(); @@ -1754,14 +1785,18 @@ pub(crate) mod test { let bob_device = alice.get_device(bob.user_id(), bob.device_id()).await.unwrap().unwrap(); - assert!(!bob_device.is_trusted()); + assert!(!bob_device.verified()); let (alice_sas, request) = bob_device.start_verification().await.unwrap(); let event = request_to_event(alice.user_id(), &request.into()); bob.handle_verification_event(&event).await; - let bob_sas = bob.get_verification(alice_sas.flow_id().as_str()).unwrap(); + let bob_sas = bob + .get_verification(alice.user_id(), alice_sas.flow_id().as_str()) + .unwrap() + .sas_v1() + .unwrap(); assert!(alice_sas.emoji().is_none()); assert!(bob_sas.emoji().is_none()); @@ -1813,14 +1848,14 @@ pub(crate) mod test { .unwrap(); assert!(alice_sas.is_done()); - assert!(bob_device.is_trusted()); + assert!(bob_device.verified()); let alice_device = bob.get_device(alice.user_id(), alice.device_id()).await.unwrap().unwrap(); - assert!(!alice_device.is_trusted()); + assert!(!alice_device.verified()); bob.handle_verification_event(&event).await; assert!(bob_sas.is_done()); - assert!(alice_device.is_trusted()); + assert!(alice_device.verified()); } } diff --git a/matrix_sdk_crypto/src/olm/account.rs b/matrix_sdk_crypto/src/olm/account.rs index 2ed7750b..2931d789 100644 --- a/matrix_sdk_crypto/src/olm/account.rs +++ b/matrix_sdk_crypto/src/olm/account.rs @@ -14,11 +14,11 @@ use std::{ collections::BTreeMap, - convert::{TryFrom, TryInto}, + convert::TryInto, fmt, ops::Deref, sync::{ - atomic::{AtomicBool, AtomicI64, Ordering}, + atomic::{AtomicBool, AtomicU64, Ordering}, Arc, }, }; @@ -30,23 +30,16 @@ use olm_rs::{ session::{OlmMessage, PreKeyMessage}, PicklingMode, }; -#[cfg(test)] -use ruma::events::EventType; use ruma::{ - api::client::r0::keys::{ - upload_keys, upload_signatures::Request as SignatureUploadRequest, OneTimeKey, SignedKey, - }, - encryption::DeviceKeys, + api::client::r0::keys::{upload_keys, upload_signatures::Request as SignatureUploadRequest}, + encryption::{DeviceKeys, OneTimeKey, SignedKey}, events::{ room::encrypted::{EncryptedEventContent, EncryptedEventScheme}, AnyToDeviceEvent, ToDeviceEvent, }, - identifiers::{ - DeviceId, DeviceIdBox, DeviceKeyAlgorithm, DeviceKeyId, EventEncryptionAlgorithm, RoomId, - UserId, - }, serde::{CanonicalJsonValue, Raw}, - UInt, + DeviceId, DeviceIdBox, DeviceKeyAlgorithm, DeviceKeyId, EventEncryptionAlgorithm, RoomId, UInt, + UserId, }; use serde::{Deserialize, Serialize}; use serde_json::{json, Value}; @@ -188,9 +181,22 @@ impl Account { } } - pub async fn update_uploaded_key_count(&self, key_count: &BTreeMap) { + pub fn update_uploaded_key_count(&self, key_count: &BTreeMap) { if let Some(count) = key_count.get(&DeviceKeyAlgorithm::SignedCurve25519) { let count: u64 = (*count).into(); + let old_count = self.inner.uploaded_key_count(); + + // Some servers might always return the key counts in the sync + // response, we don't want to the logs with noop changes if they do + // so. + if count != old_count { + debug!( + "Updated uploaded one-time key count {} -> {}.", + self.inner.uploaded_key_count(), + count + ); + } + self.inner.update_uploaded_key_count(count); } } @@ -204,16 +210,8 @@ impl Account { } self.inner.mark_as_shared(); - let one_time_key_count = - response.one_time_key_counts.get(&DeviceKeyAlgorithm::SignedCurve25519); - - let count: u64 = one_time_key_count.map_or(0, |c| (*c).into()); - debug!( - "Updated uploaded one-time key count {} -> {}, marking keys as published", - self.inner.uploaded_key_count(), - count - ); - self.inner.update_uploaded_key_count(count); + debug!("Marking one-time keys as published"); + self.update_uploaded_key_count(&response.one_time_key_counts); self.inner.mark_keys_as_published().await; self.store.save_account(self.inner.clone()).await?; @@ -439,7 +437,7 @@ pub struct ReadOnlyAccount { /// this is None, no action will be taken. After a sync request the client /// needs to set this for us, depending on the count we will suggest the /// client to upload new keys. - uploaded_signed_key_count: Arc, + uploaded_signed_key_count: Arc, } /// A typed representation of a base64 encoded string containing the account @@ -475,7 +473,7 @@ pub struct PickledAccount { /// Was the account shared. pub shared: bool, /// The number of uploaded one-time keys we have on the server. - pub uploaded_signed_key_count: i64, + pub uploaded_signed_key_count: u64, } #[cfg(not(tarpaulin_include))] @@ -506,7 +504,7 @@ impl ReadOnlyAccount { inner: Arc::new(Mutex::new(account)), identity_keys: Arc::new(identity_keys), shared: Arc::new(AtomicBool::new(false)), - uploaded_signed_key_count: Arc::new(AtomicI64::new(0)), + uploaded_signed_key_count: Arc::new(AtomicU64::new(0)), } } @@ -531,18 +529,17 @@ impl ReadOnlyAccount { /// /// * `new_count` - The new count that was reported by the server. pub(crate) fn update_uploaded_key_count(&self, new_count: u64) { - let key_count = i64::try_from(new_count).unwrap_or(i64::MAX); - self.uploaded_signed_key_count.store(key_count, Ordering::Relaxed); + self.uploaded_signed_key_count.store(new_count, Ordering::SeqCst); } /// Get the currently known uploaded key count. - pub fn uploaded_key_count(&self) -> i64 { - self.uploaded_signed_key_count.load(Ordering::Relaxed) + pub fn uploaded_key_count(&self) -> u64 { + self.uploaded_signed_key_count.load(Ordering::SeqCst) } /// Has the account been shared with the server. pub fn shared(&self) -> bool { - self.shared.load(Ordering::Relaxed) + self.shared.load(Ordering::SeqCst) } /// Mark the account as shared. @@ -550,7 +547,7 @@ impl ReadOnlyAccount { /// Messages shouldn't be encrypted with the session before it has been /// shared. pub(crate) fn mark_as_shared(&self) { - self.shared.store(true, Ordering::Relaxed); + self.shared.store(true, Ordering::SeqCst); } /// Get the one-time keys of the account. @@ -574,7 +571,7 @@ impl ReadOnlyAccount { /// /// Returns an empty error if no keys need to be uploaded. pub(crate) async fn generate_one_time_keys(&self) -> Result { - let count = self.uploaded_key_count() as u64; + let count = self.uploaded_key_count(); let max_keys = self.max_one_time_keys().await; let max_on_server = (max_keys as u64) / 2; @@ -595,7 +592,7 @@ impl ReadOnlyAccount { return true; } - let count = self.uploaded_key_count() as u64; + let count = self.uploaded_key_count(); // If we have a known key count, check that we have more than // max_one_time_Keys() / 2, otherwise tell the client to upload more. @@ -680,7 +677,7 @@ impl ReadOnlyAccount { inner: Arc::new(Mutex::new(account)), identity_keys: Arc::new(identity_keys), shared: Arc::new(AtomicBool::from(pickle.shared)), - uploaded_signed_key_count: Arc::new(AtomicI64::new(pickle.uploaded_signed_key_count)), + uploaded_signed_key_count: Arc::new(AtomicU64::new(pickle.uploaded_signed_key_count)), }) } @@ -993,6 +990,8 @@ impl ReadOnlyAccount { #[cfg(test)] pub(crate) async fn create_session_for(&self, other: &ReadOnlyAccount) -> (Session, Session) { + use ruma::events::{dummy::DummyToDeviceEventContent, AnyToDeviceEventContent}; + other.generate_one_time_keys_helper(1).await; let one_time = other.signed_one_time_keys().await.unwrap(); @@ -1003,7 +1002,10 @@ impl ReadOnlyAccount { other.mark_keys_as_published().await; - let message = our_session.encrypt(&device, EventType::Dummy, json!({})).await.unwrap(); + let message = our_session + .encrypt(&device, AnyToDeviceEventContent::Dummy(DummyToDeviceEventContent::new())) + .await + .unwrap(); let content = if let EncryptedEventScheme::OlmV1Curve25519AesSha2(c) = message.scheme { c } else { diff --git a/matrix_sdk_crypto/src/olm/group_sessions/inbound.rs b/matrix_sdk_crypto/src/olm/group_sessions/inbound.rs index e557d585..f4babb4f 100644 --- a/matrix_sdk_crypto/src/olm/group_sessions/inbound.rs +++ b/matrix_sdk_crypto/src/olm/group_sessions/inbound.rs @@ -32,8 +32,8 @@ use ruma::{ }, AnySyncRoomEvent, SyncMessageEvent, }, - identifiers::{DeviceKeyAlgorithm, EventEncryptionAlgorithm, RoomId}, serde::Raw, + DeviceKeyAlgorithm, EventEncryptionAlgorithm, RoomId, }; use serde::{Deserialize, Serialize}; use serde_json::Value; @@ -320,11 +320,9 @@ impl InboundGroupSession { decrypted_object.get_mut("content").map(|c| c.as_object_mut()).flatten() { if !decrypted_content.contains_key("m.relates_to") { - if let Some(relation) = &event.content.relates_to { - decrypted_content.insert( - "m.relates_to".to_owned(), - serde_json::to_value(relation).unwrap_or_default(), - ); + let content = serde_json::to_value(&event.content)?; + if let Some(relation) = content.as_object().and_then(|o| o.get("m.relates_to")) { + decrypted_content.insert("m.relates_to".to_owned(), relation.to_owned()); } } } diff --git a/matrix_sdk_crypto/src/olm/group_sessions/outbound.rs b/matrix_sdk_crypto/src/olm/group_sessions/outbound.rs index fc24d369..b432e44e 100644 --- a/matrix_sdk_crypto/src/olm/group_sessions/outbound.rs +++ b/matrix_sdk_crypto/src/olm/group_sessions/outbound.rs @@ -34,20 +34,20 @@ use olm_rs::{ errors::OlmGroupSessionError, outbound_group_session::OlmOutboundGroupSession, PicklingMode, }; use ruma::{ - api::client::r0::to_device::DeviceIdOrAllDevices, events::{ room::{ encrypted::{EncryptedEventContent, EncryptedEventScheme, MegolmV1AesSha2ContentInit}, encryption::EncryptionEventContent, history_visibility::HistoryVisibility, - message::Relation, }, - AnyMessageEventContent, EventContent, + room_key::RoomKeyToDeviceEventContent, + AnyMessageEventContent, AnyToDeviceEventContent, EventContent, }, + to_device::DeviceIdOrAllDevices, DeviceId, DeviceIdBox, EventEncryptionAlgorithm, RoomId, UserId, }; use serde::{Deserialize, Serialize}; -use serde_json::{json, Value}; +use serde_json::json; use tracing::{debug, error, trace}; use super::{ @@ -277,13 +277,8 @@ impl OutboundGroupSession { "type": content.event_type(), }); - let relates_to: Option = json_content - .get("content") - .map(|c| c.get("m.relates_to").cloned().map(|r| serde_json::from_value(r).ok())) - .flatten() - .flatten(); - let plaintext = json_content.to_string(); + let relation = content.relation(); let ciphertext = self.encrypt_helper(plaintext).await; @@ -297,7 +292,7 @@ impl OutboundGroupSession { EncryptedEventContent::new( EncryptedEventScheme::MegolmV1AesSha2(encrypted_content), - relates_to, + relation, ) } @@ -361,16 +356,15 @@ impl OutboundGroupSession { session.session_message_index() } - /// Get the outbound group session key as a json value that can be sent as a - /// m.room_key. - pub async fn as_json(&self) -> Value { - json!({ - "algorithm": EventEncryptionAlgorithm::MegolmV1AesSha2, - "room_id": &*self.room_id, - "session_id": &*self.session_id, - "session_key": self.session_key().await, - "chain_index": self.message_index().await, - }) + pub(crate) async fn as_content(&self) -> AnyToDeviceEventContent { + let session_key = self.session_key().await; + + AnyToDeviceEventContent::RoomKey(RoomKeyToDeviceEventContent::new( + EventEncryptionAlgorithm::MegolmV1AesSha2, + self.room_id().to_owned(), + self.session_id().to_owned(), + session_key.0.clone(), + )) } /// Has or will the session be shared with the given user/device pair. diff --git a/matrix_sdk_crypto/src/olm/mod.rs b/matrix_sdk_crypto/src/olm/mod.rs index e6fb4743..9abe2620 100644 --- a/matrix_sdk_crypto/src/olm/mod.rs +++ b/matrix_sdk_crypto/src/olm/mod.rs @@ -61,12 +61,19 @@ where pub(crate) mod test { use std::{collections::BTreeMap, convert::TryInto}; + use matches::assert_matches; use olm_rs::session::OlmMessage; use ruma::{ - api::client::r0::keys::SignedKey, - events::forwarded_room_key::ForwardedRoomKeyToDeviceEventContent, room_id, user_id, - DeviceId, UserId, + encryption::SignedKey, + event_id, + events::{ + forwarded_room_key::ForwardedRoomKeyToDeviceEventContent, + room::message::{MessageEventContent, Relation, Replacement}, + AnyMessageEventContent, AnySyncMessageEvent, AnySyncRoomEvent, + }, + room_id, user_id, DeviceId, UserId, }; + use serde_json::json; use crate::olm::{InboundGroupSession, ReadOnlyAccount, Session}; @@ -215,6 +222,71 @@ pub(crate) mod test { assert_eq!(plaintext, inbound.decrypt_helper(ciphertext).await.unwrap().0); } + #[tokio::test] + async fn edit_decryption() { + let alice = ReadOnlyAccount::new(&alice_id(), &alice_device_id()); + let room_id = room_id!("!test:localhost"); + let event_id = event_id!("$1234adfad:asdf"); + + let (outbound, _) = alice.create_group_session_pair_with_defaults(&room_id).await.unwrap(); + + assert_eq!(0, outbound.message_index().await); + assert!(!outbound.shared()); + outbound.mark_as_shared(); + assert!(outbound.shared()); + + let mut content = MessageEventContent::text_plain("Hello"); + content.relates_to = Some(Relation::Replacement(Replacement::new( + event_id.clone(), + MessageEventContent::text_plain("Hello edit").into(), + ))); + + let inbound = InboundGroupSession::new( + "test_key", + "test_key", + &room_id, + outbound.session_key().await, + None, + ) + .unwrap(); + + assert_eq!(0, inbound.first_known_index()); + + assert_eq!(outbound.session_id(), inbound.session_id()); + + let encrypted_content = + outbound.encrypt(AnyMessageEventContent::RoomMessage(content)).await; + + let event = json!({ + "sender": alice.user_id(), + "event_id": event_id, + "origin_server_ts": 0, + "room_id": room_id, + "type": "m.room.encrypted", + "content": encrypted_content, + }) + .to_string(); + + let event: AnySyncRoomEvent = serde_json::from_str(&event).expect("WHAAAT?!?!?"); + + let event = + if let AnySyncRoomEvent::Message(AnySyncMessageEvent::RoomEncrypted(event)) = event { + event + } else { + panic!("Invalid event type") + }; + + let decrypted = inbound.decrypt(&event).await.unwrap().0; + + if let AnySyncRoomEvent::Message(AnySyncMessageEvent::RoomMessage(e)) = + decrypted.deserialize().unwrap() + { + assert_matches!(e.content.relates_to, Some(Relation::Replacement(_))); + } else { + panic!("Invalid event type") + } + } + #[tokio::test] async fn group_session_export() { let alice = ReadOnlyAccount::new(&alice_id(), &alice_device_id()); diff --git a/matrix_sdk_crypto/src/olm/session.rs b/matrix_sdk_crypto/src/olm/session.rs index be34a376..a34d901f 100644 --- a/matrix_sdk_crypto/src/olm/session.rs +++ b/matrix_sdk_crypto/src/olm/session.rs @@ -26,12 +26,12 @@ use ruma::{ CiphertextInfo, EncryptedEventContent, EncryptedEventScheme, OlmV1Curve25519AesSha2Content, }, - EventType, + AnyToDeviceEventContent, EventContent, }, - identifiers::{DeviceId, DeviceKeyAlgorithm, UserId}, + DeviceId, DeviceKeyAlgorithm, UserId, }; use serde::{Deserialize, Serialize}; -use serde_json::{json, Value}; +use serde_json::json; use super::{deserialize_instant, serialize_instant, IdentityKeys}; use crate::{ @@ -105,21 +105,17 @@ impl Session { /// encrypted, this needs to be the device that was used to create this /// session with. /// - /// * `event_type` - The type of the event. - /// /// * `content` - The content of the event. pub async fn encrypt( &mut self, recipient_device: &ReadOnlyDevice, - event_type: EventType, - content: Value, + content: AnyToDeviceEventContent, ) -> OlmResult { let recipient_signing_key = recipient_device .get_key(DeviceKeyAlgorithm::Ed25519) .ok_or(EventError::MissingSigningKey)?; - let relates_to = - content.get("m.relates_to").cloned().and_then(|v| serde_json::from_value(v).ok()); + let event_type = content.event_type(); let payload = json!({ "sender": self.user_id.as_str(), @@ -149,7 +145,7 @@ impl Session { content, self.our_identity_keys.curve25519().to_string(), )), - relates_to, + None, )) } diff --git a/matrix_sdk_crypto/src/olm/signing/mod.rs b/matrix_sdk_crypto/src/olm/signing/mod.rs index 7f4581cd..469cdbce 100644 --- a/matrix_sdk_crypto/src/olm/signing/mod.rs +++ b/matrix_sdk_crypto/src/olm/signing/mod.rs @@ -25,16 +25,16 @@ use std::{ use matrix_sdk_common::locks::Mutex; use pk_signing::{MasterSigning, PickledSignings, SelfSigning, Signing, SigningError, UserSigning}; use ruma::{ - api::client::r0::keys::{upload_signatures::Request as SignatureUploadRequest, KeyUsage}, - encryption::DeviceKeys, + api::client::r0::keys::upload_signatures::Request as SignatureUploadRequest, + encryption::{DeviceKeys, KeyUsage}, DeviceKeyAlgorithm, DeviceKeyId, UserId, }; use serde::{Deserialize, Serialize}; use serde_json::Error as JsonError; use crate::{ - error::SignatureError, requests::UploadSigningKeysRequest, OwnUserIdentity, ReadOnlyAccount, - ReadOnlyDevice, UserIdentity, + error::SignatureError, identities::MasterPubkey, requests::UploadSigningKeysRequest, + ReadOnlyAccount, ReadOnlyDevice, ReadOnlyOwnUserIdentity, ReadOnlyUserIdentity, }; /// Private cross signing identity. @@ -91,6 +91,21 @@ impl PrivateCrossSigningIdentity { !(has_master && has_user && has_self) } + /// Can we sign our own devices, i.e. do we have a self signing key. + pub async fn can_sign_devices(&self) -> bool { + self.self_signing_key.lock().await.is_some() + } + + /// Do we have the master key. + pub async fn has_master_key(&self) -> bool { + self.master_key.lock().await.is_some() + } + + /// Get the public part of the master key, if we have one. + pub async fn master_public_key(&self) -> Option { + self.master_key.lock().await.as_ref().map(|m| m.public_key.to_owned()) + } + /// Create a new empty identity. pub(crate) fn empty(user_id: UserId) -> Self { Self { @@ -102,7 +117,9 @@ impl PrivateCrossSigningIdentity { } } - pub(crate) async fn as_public_identity(&self) -> Result { + pub(crate) async fn to_public_identity( + &self, + ) -> Result { let master = self .master_key .lock() @@ -127,7 +144,7 @@ impl PrivateCrossSigningIdentity { .ok_or(SignatureError::MissingSigningKey)? .public_key .clone(); - let identity = OwnUserIdentity::new(master, self_signing, user_signing)?; + let identity = ReadOnlyOwnUserIdentity::new(master, self_signing, user_signing)?; identity.mark_as_verified(); Ok(identity) @@ -136,7 +153,7 @@ impl PrivateCrossSigningIdentity { /// Sign the given public user identity with this private identity. pub(crate) async fn sign_user( &self, - user_identity: &UserIdentity, + user_identity: &ReadOnlyUserIdentity, ) -> Result { let signed_keys = self .user_signing_key @@ -388,11 +405,11 @@ mod test { use std::{collections::BTreeMap, sync::Arc}; use matrix_sdk_test::async_test; - use ruma::{api::client::r0::keys::CrossSigningKey, user_id, UserId}; + use ruma::{encryption::CrossSigningKey, user_id, UserId}; use super::{PrivateCrossSigningIdentity, Signing}; use crate::{ - identities::{ReadOnlyDevice, UserIdentity}, + identities::{ReadOnlyDevice, ReadOnlyUserIdentity}, olm::ReadOnlyAccount, }; @@ -503,7 +520,7 @@ mod test { let bob_account = ReadOnlyAccount::new(&user_id!("@bob:localhost"), "DEVICEID".into()); let (bob_private, _, _) = PrivateCrossSigningIdentity::new_with_account(&bob_account).await; - let mut bob_public = UserIdentity::from_private(&bob_private).await; + let mut bob_public = ReadOnlyUserIdentity::from_private(&bob_private).await; let user_signing = identity.user_signing_key.lock().await; let user_signing = user_signing.as_ref().unwrap(); diff --git a/matrix_sdk_crypto/src/olm/signing/pk_signing.rs b/matrix_sdk_crypto/src/olm/signing/pk_signing.rs index 0da882d3..61ba5bcd 100644 --- a/matrix_sdk_crypto/src/olm/signing/pk_signing.rs +++ b/matrix_sdk_crypto/src/olm/signing/pk_signing.rs @@ -24,8 +24,7 @@ use olm_rs::pk::OlmPkSigning; #[cfg(test)] use olm_rs::{errors::OlmUtilityError, utility::OlmUtility}; use ruma::{ - api::client::r0::keys::{CrossSigningKey, KeyUsage}, - encryption::DeviceKeys, + encryption::{CrossSigningKey, DeviceKeys, KeyUsage}, serde::CanonicalJsonValue, DeviceKeyAlgorithm, DeviceKeyId, UserId, }; @@ -38,7 +37,7 @@ use crate::{ error::SignatureError, identities::{MasterPubkey, SelfSigningPubkey, UserSigningPubkey}, utilities::{decode_url_safe as decode, encode_url_safe as encode, DecodeError}, - UserIdentity, + ReadOnlyUserIdentity, }; const NONCE_SIZE: usize = 12; @@ -187,7 +186,7 @@ impl UserSigning { pub async fn sign_user( &self, - user: &UserIdentity, + user: &ReadOnlyUserIdentity, ) -> Result>, SignatureError> { let user_master: &CrossSigningKey = user.master_key().as_ref(); let signature = self.inner.sign_json(serde_json::to_value(user_master)?).await?; diff --git a/matrix_sdk_crypto/src/requests.rs b/matrix_sdk_crypto/src/requests.rs index fc3faa9b..79981b26 100644 --- a/matrix_sdk_crypto/src/requests.rs +++ b/matrix_sdk_crypto/src/requests.rs @@ -12,8 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -#![allow(missing_docs)] - use std::{collections::BTreeMap, sync::Arc, time::Duration}; use matrix_sdk_common::uuid::Uuid; @@ -27,16 +25,17 @@ use ruma::{ Request as SignatureUploadRequest, Response as SignatureUploadResponse, }, upload_signing_keys::Response as SigningKeysUploadResponse, - CrossSigningKey, }, message::send_message_event::Response as RoomMessageResponse, - to_device::{send_event_to_device::Response as ToDeviceResponse, DeviceIdOrAllDevices}, + to_device::send_event_to_device::Response as ToDeviceResponse, }, - events::{AnyMessageEventContent, EventType}, + encryption::CrossSigningKey, + events::{AnyMessageEventContent, AnyToDeviceEventContent, EventContent, EventType}, + serde::Raw, + to_device::DeviceIdOrAllDevices, DeviceIdBox, RoomId, UserId, }; use serde::{Deserialize, Serialize}; -use serde_json::value::RawValue as RawJsonValue; /// Customized version of /// `ruma_client_api::r0::to_device::send_event_to_device::Request`, @@ -56,10 +55,68 @@ pub struct ToDeviceRequest { /// The content's type for this field will be updated in a future /// release, until then you can create a value using /// `serde_json::value::to_raw_value`. - pub messages: BTreeMap>>, + pub messages: BTreeMap>>, } impl ToDeviceRequest { + /// Create a new owned to-device request + /// + /// # Arguments + /// + /// * `recipient` - The ID of the user that should receive this to-device + /// event. + /// + /// * `recipient_device` - The device that should receive this to-device + /// event, or all devices. + /// + /// * `content` - The content of the to-device event. + pub(crate) fn new( + recipient: &UserId, + recipient_device: impl Into, + content: AnyToDeviceEventContent, + ) -> Self { + Self::new_with_id(recipient, recipient_device, content, Uuid::new_v4()) + } + + pub(crate) fn new_for_recipients( + recipient: &UserId, + recipient_devices: Vec, + content: AnyToDeviceEventContent, + txn_id: Uuid, + ) -> Self { + let mut messages = BTreeMap::new(); + let event_type = EventType::from(content.event_type()); + + if recipient_devices.is_empty() { + Self::new(recipient, DeviceIdOrAllDevices::AllDevices, content) + } else { + let device_messages = recipient_devices + .into_iter() + .map(|d| (DeviceIdOrAllDevices::DeviceId(d), Raw::from(content.clone()))) + .collect(); + + messages.insert(recipient.clone(), device_messages); + + ToDeviceRequest { event_type, txn_id, messages } + } + } + + pub(crate) fn new_with_id( + recipient: &UserId, + recipient_device: impl Into, + content: AnyToDeviceEventContent, + txn_id: Uuid, + ) -> Self { + let mut messages = BTreeMap::new(); + let mut user_messages = BTreeMap::new(); + let event_type = EventType::from(content.event_type()); + + user_messages.insert(recipient_device.into(), Raw::from(content)); + messages.insert(recipient.clone(), user_messages); + + ToDeviceRequest { event_type, txn_id, messages } + } + /// Gets the transaction ID as a string. pub fn txn_id_string(&self) -> String { self.txn_id.to_string() @@ -133,6 +190,8 @@ pub enum OutgoingRequests { /// Signature upload request, this request is used after a successful device /// or user verification is done. SignatureUpload(SignatureUploadRequest), + /// A room message request, usually for sending in-room interactive + /// verification events. RoomMessage(RoomMessageRequest), } @@ -205,9 +264,9 @@ pub enum IncomingResponse<'a> { /// The cross signing keys upload response, marking our private cross /// signing identity as shared. SigningKeysUpload(&'a SigningKeysUploadResponse), - /// The cross signing keys upload response, marking our private cross - /// signing identity as shared. + /// The cross signing signature upload response. SignatureUpload(&'a SignatureUploadResponse), + /// A room message response, usually for interactive verifications. RoomMessage(&'a RoomMessageResponse), } @@ -270,6 +329,7 @@ impl OutgoingRequest { } } +/// Customized owned request type for sending out room messages. #[derive(Clone, Debug)] pub struct RoomMessageRequest { /// The room to send the event to. @@ -286,13 +346,17 @@ pub struct RoomMessageRequest { pub content: AnyMessageEventContent, } +/// An enum over the different outgoing verification based requests. #[derive(Clone, Debug)] pub enum OutgoingVerificationRequest { + /// The to-device verification request variant. ToDevice(ToDeviceRequest), + /// The in-room verification request variant. InRoom(RoomMessageRequest), } impl OutgoingVerificationRequest { + /// Get the unique id of this request. pub fn request_id(&self) -> Uuid { match self { OutgoingVerificationRequest::ToDevice(t) => t.txn_id, diff --git a/matrix_sdk_crypto/src/session_manager/group_sessions.rs b/matrix_sdk_crypto/src/session_manager/group_sessions.rs index 26269716..e9b6bd3f 100644 --- a/matrix_sdk_crypto/src/session_manager/group_sessions.rs +++ b/matrix_sdk_crypto/src/session_manager/group_sessions.rs @@ -21,14 +21,14 @@ use dashmap::DashMap; use futures::future::join_all; use matrix_sdk_common::{executor::spawn, uuid::Uuid}; use ruma::{ - api::client::r0::to_device::DeviceIdOrAllDevices, events::{ room::{encrypted::EncryptedEventContent, history_visibility::HistoryVisibility}, - AnyMessageEventContent, EventType, + AnyMessageEventContent, AnyToDeviceEventContent, EventType, }, + serde::Raw, + to_device::DeviceIdOrAllDevices, DeviceId, DeviceIdBox, RoomId, UserId, }; -use serde_json::Value; use tracing::{debug, info, trace}; use crate::{ @@ -146,11 +146,6 @@ impl GroupSessionManager { let mut changes = Changes::default(); changes.outbound_group_sessions.push(s.clone()); self.store.save_changes(changes).await?; - } else { - trace!( - request_id = request_id.to_string().as_str(), - "Marking room key share request as sent but session found that owns the given id" - ) } Ok(()) @@ -229,22 +224,22 @@ impl GroupSessionManager { /// Encrypt the given content for the given devices and create a to-device /// requests that sends the encrypted content to them. async fn encrypt_session_for( - content: Value, + content: AnyToDeviceEventContent, devices: Vec, ) -> OlmResult<(Uuid, ToDeviceRequest, Vec)> { let mut messages = BTreeMap::new(); let mut changed_sessions = Vec::new(); - let encrypt = |device: Device, content: Value| async move { + let encrypt = |device: Device, content: AnyToDeviceEventContent| async move { let mut message = BTreeMap::new(); - let encrypted = device.encrypt(EventType::RoomKey, content.clone()).await; + let encrypted = device.encrypt(content.clone()).await; let used_session = match encrypted { Ok((session, encrypted)) => { message.entry(device.user_id().clone()).or_insert_with(BTreeMap::new).insert( DeviceIdOrAllDevices::DeviceId(device.device_id().into()), - serde_json::value::to_raw_value(&encrypted)?, + Raw::from(AnyToDeviceEventContent::RoomEncrypted(encrypted)), ); Some(session) } @@ -380,7 +375,7 @@ impl GroupSessionManager { pub async fn encrypt_request( chunk: Vec, - content: Value, + content: AnyToDeviceEventContent, outbound: OutboundGroupSession, message_index: u32, being_shared: Arc>, @@ -417,10 +412,7 @@ impl GroupSessionManager { users: impl Iterator, encryption_settings: impl Into, ) -> OlmResult>> { - debug!( - room_id = room_id.as_str(), - "Checking if a group session needs to be shared for room {}", room_id - ); + debug!(room_id = room_id.as_str(), "Checking if a room key needs to be shared",); let encryption_settings = encryption_settings.into(); let history_visibility = encryption_settings.history_visibility.clone(); @@ -471,7 +463,7 @@ impl GroupSessionManager { .flatten() .collect(); - let key_content = outbound.as_json().await; + let key_content = outbound.as_content().await; let message_index = outbound.message_index().await; if !devices.is_empty() { diff --git a/matrix_sdk_crypto/src/session_manager/sessions.rs b/matrix_sdk_crypto/src/session_manager/sessions.rs index 657de170..3b742e7f 100644 --- a/matrix_sdk_crypto/src/session_manager/sessions.rs +++ b/matrix_sdk_crypto/src/session_manager/sessions.rs @@ -17,15 +17,13 @@ use std::{collections::BTreeMap, sync::Arc, time::Duration}; use dashmap::{DashMap, DashSet}; use matrix_sdk_common::uuid::Uuid; use ruma::{ - api::client::r0::{ - keys::claim_keys::{Request as KeysClaimRequest, Response as KeysClaimResponse}, - to_device::DeviceIdOrAllDevices, + api::client::r0::keys::claim_keys::{ + Request as KeysClaimRequest, Response as KeysClaimResponse, }, assign, - events::EventType, + events::{dummy::DummyToDeviceEventContent, AnyToDeviceEventContent}, DeviceId, DeviceIdBox, DeviceKeyAlgorithm, UserId, }; -use serde_json::{json, value::to_raw_value}; use tracing::{error, info, warn}; use crate::{ @@ -118,28 +116,21 @@ impl SessionManager { async fn check_if_unwedged(&self, user_id: &UserId, device_id: &DeviceId) -> OlmResult<()> { if self.wedged_devices.get(user_id).map(|d| d.remove(device_id)).flatten().is_some() { if let Some(device) = self.store.get_device(user_id, device_id).await? { - let (_, content) = device.encrypt(EventType::Dummy, json!({})).await?; - let id = Uuid::new_v4(); - let mut messages = BTreeMap::new(); + let content = AnyToDeviceEventContent::Dummy(DummyToDeviceEventContent::new()); + let (_, content) = device.encrypt(content).await?; - messages.entry(device.user_id().to_owned()).or_insert_with(BTreeMap::new).insert( - DeviceIdOrAllDevices::DeviceId(device.device_id().into()), - to_raw_value(&content)?, + let request = ToDeviceRequest::new( + device.user_id(), + device.device_id().to_owned(), + AnyToDeviceEventContent::RoomEncrypted(content), ); let request = OutgoingRequest { - request_id: id, - request: Arc::new( - ToDeviceRequest { - event_type: EventType::RoomEncrypted, - txn_id: id, - messages, - } - .into(), - ), + request_id: request.txn_id, + request: Arc::new(request.into()), }; - self.outgoing_to_device_requests.insert(id, request); + self.outgoing_to_device_requests.insert(request.request_id, request); } } diff --git a/matrix_sdk_crypto/src/store/memorystore.rs b/matrix_sdk_crypto/src/store/memorystore.rs index 348b0c79..ef52fd44 100644 --- a/matrix_sdk_crypto/src/store/memorystore.rs +++ b/matrix_sdk_crypto/src/store/memorystore.rs @@ -26,7 +26,7 @@ use super::{ Changes, CryptoStore, InboundGroupSession, ReadOnlyAccount, Result, Session, }; use crate::{ - identities::{ReadOnlyDevice, UserIdentities}, + identities::{ReadOnlyDevice, ReadOnlyUserIdentities}, key_request::OutgoingKeyRequest, olm::{OutboundGroupSession, PrivateCrossSigningIdentity}, }; @@ -44,7 +44,7 @@ pub struct MemoryStore { users_for_key_query: Arc>, olm_hashes: Arc>>, devices: DeviceStore, - identities: Arc>, + identities: Arc>, outgoing_key_requests: Arc>, key_requests_by_info: Arc>, } @@ -215,7 +215,7 @@ impl CryptoStore for MemoryStore { Ok(self.devices.user_devices(user_id)) } - async fn get_user_identity(&self, user_id: &UserId) -> Result> { + async fn get_user_identity(&self, user_id: &UserId) -> Result> { #[allow(clippy::map_clone)] Ok(self.identities.get(user_id).map(|i| i.clone())) } diff --git a/matrix_sdk_crypto/src/store/mod.rs b/matrix_sdk_crypto/src/store/mod.rs index 1f41609d..74f5aacf 100644 --- a/matrix_sdk_crypto/src/store/mod.rs +++ b/matrix_sdk_crypto/src/store/mod.rs @@ -56,11 +56,8 @@ pub use memorystore::MemoryStore; use olm_rs::errors::{OlmAccountError, OlmGroupSessionError, OlmSessionError}; pub use pickle_key::{EncryptedPickleKey, PickleKey}; use ruma::{ - events::room_key_request::RequestedKeyInfo, - identifiers::{ - DeviceId, DeviceIdBox, DeviceKeyAlgorithm, Error as IdentifierValidationError, RoomId, - UserId, - }, + events::room_key_request::RequestedKeyInfo, identifiers::Error as IdentifierValidationError, + DeviceId, DeviceIdBox, DeviceKeyAlgorithm, RoomId, UserId, }; use serde_json::Error as SerdeError; use thiserror::Error; @@ -69,7 +66,10 @@ use thiserror::Error; pub use self::sled::SledStore; use crate::{ error::SessionUnpicklingError, - identities::{Device, ReadOnlyDevice, UserDevices, UserIdentities}, + identities::{ + user::{OwnUserIdentity, UserIdentities, UserIdentity}, + Device, ReadOnlyDevice, ReadOnlyUserIdentities, UserDevices, + }, key_request::OutgoingKeyRequest, olm::{ InboundGroupSession, OlmMessageHash, OutboundGroupSession, PrivateCrossSigningIdentity, @@ -112,8 +112,8 @@ pub struct Changes { #[derive(Debug, Clone, Default)] #[allow(missing_docs)] pub struct IdentityChanges { - pub new: Vec, - pub changed: Vec, + pub new: Vec, + pub changed: Vec, } #[derive(Debug, Clone, Default)] @@ -218,8 +218,8 @@ impl Store { device_id: &DeviceId, ) -> Result> { let own_identity = - self.get_user_identity(&self.user_id).await?.map(|i| i.own().cloned()).flatten(); - let device_owner_identity = self.get_user_identity(user_id).await?; + self.inner.get_user_identity(&self.user_id).await?.map(|i| i.own().cloned()).flatten(); + let device_owner_identity = self.inner.get_user_identity(user_id).await?; Ok(self.inner.get_device(user_id, device_id).await?.map(|d| Device { inner: d, @@ -229,6 +229,20 @@ impl Store { device_owner_identity, })) } + + pub async fn get_identity(&self, user_id: &UserId) -> Result> { + Ok(self.inner.get_user_identity(user_id).await?.map(|i| match i { + ReadOnlyUserIdentities::Own(i) => OwnUserIdentity { + inner: i, + verification_machine: self.verification_machine.clone(), + } + .into(), + ReadOnlyUserIdentities::Other(i) => { + UserIdentity { inner: i, verification_machine: self.verification_machine.clone() } + .into() + } + })) + } } impl Deref for Store { @@ -239,7 +253,7 @@ impl Deref for Store { } } -#[derive(Error, Debug)] +#[derive(Debug, Error)] /// The crypto store's error type. pub enum CryptoStoreError { /// The account that owns the sessions, group sessions, and devices wasn't @@ -391,7 +405,7 @@ pub trait CryptoStore: AsyncTraitDeps { /// # Arguments /// /// * `user_id` - The user for which we should get the identity. - async fn get_user_identity(&self, user_id: &UserId) -> Result>; + async fn get_user_identity(&self, user_id: &UserId) -> Result>; /// Check if a hash for an Olm message stored in the database. async fn is_message_known(&self, message_hash: &OlmMessageHash) -> Result; diff --git a/matrix_sdk_crypto/src/store/sled.rs b/matrix_sdk_crypto/src/store/sled.rs index afa41a05..9ed76e07 100644 --- a/matrix_sdk_crypto/src/store/sled.rs +++ b/matrix_sdk_crypto/src/store/sled.rs @@ -35,7 +35,7 @@ use super::{ ReadOnlyAccount, Result, Session, }; use crate::{ - identities::{ReadOnlyDevice, UserIdentities}, + identities::{ReadOnlyDevice, ReadOnlyUserIdentities}, key_request::OutgoingKeyRequest, olm::{OutboundGroupSession, PickledInboundGroupSession, PrivateCrossSigningIdentity}, }; @@ -669,7 +669,7 @@ impl CryptoStore for SledStore { .collect() } - async fn get_user_identity(&self, user_id: &UserId) -> Result> { + async fn get_user_identity(&self, user_id: &UserId) -> Result> { Ok(self .identities .get(user_id.encode())? @@ -757,9 +757,8 @@ mod test { use matrix_sdk_test::async_test; use olm_rs::outbound_group_session::OlmOutboundGroupSession; use ruma::{ - api::client::r0::keys::SignedKey, - events::room_key_request::RequestedKeyInfo, - identifiers::{room_id, user_id, DeviceId, EventEncryptionAlgorithm, UserId}, + encryption::SignedKey, events::room_key_request::RequestedKeyInfo, room_id, user_id, + DeviceId, EventEncryptionAlgorithm, UserId, }; use tempfile::tempdir; diff --git a/matrix_sdk_crypto/src/verification/cache.rs b/matrix_sdk_crypto/src/verification/cache.rs index 975f9fd6..2124969b 100644 --- a/matrix_sdk_crypto/src/verification/cache.rs +++ b/matrix_sdk_crypto/src/verification/cache.rs @@ -17,13 +17,17 @@ use std::sync::Arc; use dashmap::DashMap; use matrix_sdk_common::uuid::Uuid; use ruma::{DeviceId, UserId}; +use tracing::trace; -use super::{event_enums::OutgoingContent, sas::content_to_request, Sas, Verification}; -use crate::{OutgoingRequest, RoomMessageRequest}; +use super::{event_enums::OutgoingContent, Sas, Verification}; +use crate::{ + OutgoingRequest, OutgoingVerificationRequest, QrVerification, RoomMessageRequest, + ToDeviceRequest, +}; #[derive(Clone, Debug)] pub struct VerificationCache { - verification: Arc>, + verification: Arc>>, outgoing_requests: Arc>, } @@ -35,41 +39,75 @@ impl VerificationCache { #[cfg(test)] #[allow(dead_code)] pub fn is_empty(&self) -> bool { - self.verification.is_empty() + self.verification.iter().all(|m| m.is_empty()) + } + + pub fn insert(&self, verification: impl Into) { + let verification = verification.into(); + + self.verification + .entry(verification.other_user().to_owned()) + .or_insert_with(DashMap::new) + .insert(verification.flow_id().to_owned(), verification); } pub fn insert_sas(&self, sas: Sas) { - self.verification.insert(sas.flow_id().as_str().to_string(), sas.into()); + self.insert(sas); + } + + pub fn insert_qr(&self, qr: QrVerification) { + self.insert(qr) + } + + pub fn get_qr(&self, sender: &UserId, flow_id: &str) -> Option { + self.get(sender, flow_id).and_then(|v| { + if let Verification::QrV1(qr) = v { + Some(qr) + } else { + None + } + }) + } + + pub fn get(&self, sender: &UserId, flow_id: &str) -> Option { + self.verification.get(sender).and_then(|m| m.get(flow_id).map(|v| v.clone())) } pub fn outgoing_requests(&self) -> Vec { self.outgoing_requests.iter().map(|r| (*r).clone()).collect() } - pub fn garbage_collect(&self) -> Vec { - self.verification.retain(|_, s| !(s.is_done() || s.is_cancelled())); + pub fn garbage_collect(&self) -> Vec { + for user_verification in self.verification.iter() { + user_verification.retain(|_, s| !(s.is_done() || s.is_cancelled())); + } + + self.verification.retain(|_, m| !m.is_empty()); self.verification .iter() - .filter_map(|s| { - #[allow(irrefutable_let_patterns)] - if let Verification::SasV1(s) = s.value() { - s.cancel_if_timed_out().map(|r| OutgoingRequest { - request_id: r.request_id(), - request: Arc::new(r.into()), + .flat_map(|v| { + let requests: Vec = v + .value() + .iter() + .filter_map(|s| { + if let Verification::SasV1(s) = s.value() { + s.cancel_if_timed_out() + } else { + None + } }) - } else { - None - } + .collect(); + + requests }) .collect() } - pub fn get_sas(&self, transaction_id: &str) -> Option { - self.verification.get(transaction_id).and_then(|v| { - #[allow(irrefutable_let_patterns)] - if let Verification::SasV1(sas) = v.value() { - Some(sas.clone()) + pub fn get_sas(&self, user_id: &UserId, flow_id: &str) -> Option { + self.get(user_id, flow_id).and_then(|v| { + if let Verification::SasV1(sas) = v { + Some(sas) } else { None } @@ -77,9 +115,16 @@ impl VerificationCache { } pub fn add_request(&self, request: OutgoingRequest) { + trace!("Adding an outgoing verification request {:?}", request); self.outgoing_requests.insert(request.request_id, request); } + pub fn add_verification_request(&self, request: OutgoingVerificationRequest) { + let request = + OutgoingRequest { request_id: request.request_id(), request: Arc::new(request.into()) }; + self.add_request(request); + } + pub fn queue_up_content( &self, recipient: &UserId, @@ -88,7 +133,7 @@ impl VerificationCache { ) { match content { OutgoingContent::ToDevice(c) => { - let request = content_to_request(recipient, recipient_device.to_owned(), c); + let request = ToDeviceRequest::new(recipient, recipient_device.to_owned(), c); let request_id = request.txn_id; let request = OutgoingRequest { request_id, request: Arc::new(request.into()) }; diff --git a/matrix_sdk_crypto/src/verification/event_enums.rs b/matrix_sdk_crypto/src/verification/event_enums.rs index c623a29a..0a01c8ad 100644 --- a/matrix_sdk_crypto/src/verification/event_enums.rs +++ b/matrix_sdk_crypto/src/verification/event_enums.rs @@ -33,8 +33,8 @@ use ruma::{ room::message::{KeyVerificationRequestEventContent, MessageType}, AnyMessageEvent, AnyMessageEventContent, AnyToDeviceEvent, AnyToDeviceEventContent, }, - identifiers::{DeviceId, RoomId, UserId}, serde::CanonicalJsonValue, + DeviceId, MilliSecondsSinceUnixEpoch, RoomId, UserId, }; use super::FlowId; @@ -53,6 +53,20 @@ impl AnyEvent<'_> { } } + pub fn timestamp(&self) -> Option<&MilliSecondsSinceUnixEpoch> { + match self { + AnyEvent::Room(e) => Some(e.origin_server_ts()), + AnyEvent::ToDevice(e) => match e { + AnyToDeviceEvent::KeyVerificationRequest(e) => Some(&e.content.timestamp), + _ => None, + }, + } + } + + pub fn is_room_event(&self) -> bool { + matches!(self, AnyEvent::Room(_)) + } + pub fn verification_content(&self) -> Option { match self { AnyEvent::Room(e) => match e { @@ -64,8 +78,7 @@ impl AnyEvent<'_> { | AnyMessageEvent::RoomEncrypted(_) | AnyMessageEvent::RoomMessageFeedback(_) | AnyMessageEvent::RoomRedaction(_) - | AnyMessageEvent::Sticker(_) - | AnyMessageEvent::Custom(_) => None, + | AnyMessageEvent::Sticker(_) => None, AnyMessageEvent::RoomMessage(m) => { if let MessageType::VerificationRequest(v) = &m.content.msgtype { Some(RequestContent::from(v).into()) @@ -90,14 +103,14 @@ impl AnyEvent<'_> { AnyMessageEvent::KeyVerificationDone(e) => { Some(DoneContent::from(&e.content).into()) } + _ => None, }, AnyEvent::ToDevice(e) => match e { AnyToDeviceEvent::Dummy(_) | AnyToDeviceEvent::RoomKey(_) | AnyToDeviceEvent::RoomKeyRequest(_) | AnyToDeviceEvent::ForwardedRoomKey(_) - | AnyToDeviceEvent::RoomEncrypted(_) - | AnyToDeviceEvent::Custom(_) => None, + | AnyToDeviceEvent::RoomEncrypted(_) => None, AnyToDeviceEvent::KeyVerificationRequest(e) => { Some(RequestContent::from(&e.content).into()) } @@ -122,6 +135,7 @@ impl AnyEvent<'_> { AnyToDeviceEvent::KeyVerificationDone(e) => { Some(DoneContent::from(&e.content).into()) } + _ => None, }, } } @@ -163,30 +177,30 @@ impl TryFrom<&AnyMessageEvent> for FlowId { | AnyMessageEvent::RoomEncrypted(_) | AnyMessageEvent::RoomMessageFeedback(_) | AnyMessageEvent::RoomRedaction(_) - | AnyMessageEvent::Sticker(_) - | AnyMessageEvent::Custom(_) => Err(()), + | AnyMessageEvent::Sticker(_) => Err(()), AnyMessageEvent::KeyVerificationReady(e) => { - Ok(FlowId::from((&e.room_id, &e.content.relation.event_id))) + Ok(FlowId::from((&e.room_id, &e.content.relates_to.event_id))) } AnyMessageEvent::RoomMessage(e) => Ok(FlowId::from((&e.room_id, &e.event_id))), AnyMessageEvent::KeyVerificationStart(e) => { - Ok(FlowId::from((&e.room_id, &e.content.relation.event_id))) + Ok(FlowId::from((&e.room_id, &e.content.relates_to.event_id))) } AnyMessageEvent::KeyVerificationCancel(e) => { - Ok(FlowId::from((&e.room_id, &e.content.relation.event_id))) + Ok(FlowId::from((&e.room_id, &e.content.relates_to.event_id))) } AnyMessageEvent::KeyVerificationAccept(e) => { - Ok(FlowId::from((&e.room_id, &e.content.relation.event_id))) + Ok(FlowId::from((&e.room_id, &e.content.relates_to.event_id))) } AnyMessageEvent::KeyVerificationKey(e) => { - Ok(FlowId::from((&e.room_id, &e.content.relation.event_id))) + Ok(FlowId::from((&e.room_id, &e.content.relates_to.event_id))) } AnyMessageEvent::KeyVerificationMac(e) => { - Ok(FlowId::from((&e.room_id, &e.content.relation.event_id))) + Ok(FlowId::from((&e.room_id, &e.content.relates_to.event_id))) } AnyMessageEvent::KeyVerificationDone(e) => { - Ok(FlowId::from((&e.room_id, &e.content.relation.event_id))) + Ok(FlowId::from((&e.room_id, &e.content.relates_to.event_id))) } + _ => Err(()), } } } @@ -200,8 +214,7 @@ impl TryFrom<&AnyToDeviceEvent> for FlowId { | AnyToDeviceEvent::RoomKey(_) | AnyToDeviceEvent::RoomKeyRequest(_) | AnyToDeviceEvent::ForwardedRoomKey(_) - | AnyToDeviceEvent::RoomEncrypted(_) - | AnyToDeviceEvent::Custom(_) => Err(()), + | AnyToDeviceEvent::RoomEncrypted(_) => Err(()), AnyToDeviceEvent::KeyVerificationRequest(e) => { Ok(FlowId::from(e.content.transaction_id.to_owned())) } @@ -226,6 +239,7 @@ impl TryFrom<&AnyToDeviceEvent> for FlowId { AnyToDeviceEvent::KeyVerificationDone(e) => { Ok(FlowId::from(e.content.transaction_id.to_owned())) } + _ => Err(()), } } } @@ -365,6 +379,33 @@ try_from_outgoing_content!(MacContent, KeyVerificationMac); try_from_outgoing_content!(CancelContent, KeyVerificationCancel); try_from_outgoing_content!(DoneContent, KeyVerificationDone); +impl<'a> TryFrom<&'a OutgoingContent> for RequestContent<'a> { + type Error = (); + + fn try_from(value: &'a OutgoingContent) -> Result { + match value { + OutgoingContent::Room(_, c) => { + if let AnyMessageEventContent::RoomMessage(m) = c { + if let MessageType::VerificationRequest(c) = &m.msgtype { + Ok(Self::Room(c)) + } else { + Err(()) + } + } else { + Err(()) + } + } + OutgoingContent::ToDevice(c) => { + if let AnyToDeviceEventContent::KeyVerificationRequest(c) = c { + Ok(Self::ToDevice(c)) + } else { + Err(()) + } + } + } + } +} + #[derive(Debug)] pub enum StartContent<'a> { ToDevice(&'a StartToDeviceEventContent), @@ -382,7 +423,7 @@ impl<'a> StartContent<'a> { pub fn flow_id(&self) -> &str { match self { Self::ToDevice(c) => &c.transaction_id, - Self::Room(c) => c.relation.event_id.as_str(), + Self::Room(c) => c.relates_to.event_id.as_str(), } } @@ -434,7 +475,7 @@ impl<'a> DoneContent<'a> { pub fn flow_id(&self) -> &str { match self { Self::ToDevice(c) => &c.transaction_id, - Self::Room(c) => c.relation.event_id.as_str(), + Self::Room(c) => c.relates_to.event_id.as_str(), } } } @@ -449,7 +490,7 @@ impl AcceptContent<'_> { pub fn flow_id(&self) -> &str { match self { Self::ToDevice(c) => &c.transaction_id, - Self::Room(c) => c.relation.event_id.as_str(), + Self::Room(c) => c.relates_to.event_id.as_str(), } } @@ -480,7 +521,7 @@ impl KeyContent<'_> { pub fn flow_id(&self) -> &str { match self { Self::ToDevice(c) => &c.transaction_id, - Self::Room(c) => c.relation.event_id.as_str(), + Self::Room(c) => c.relates_to.event_id.as_str(), } } @@ -502,7 +543,7 @@ impl MacContent<'_> { pub fn flow_id(&self) -> &str { match self { Self::ToDevice(c) => &c.transaction_id, - Self::Room(c) => c.relation.event_id.as_str(), + Self::Room(c) => c.relates_to.event_id.as_str(), } } @@ -567,7 +608,7 @@ impl OwnedStartContent { pub fn flow_id(&self) -> FlowId { match self { Self::ToDevice(c) => FlowId::ToDevice(c.transaction_id.clone()), - Self::Room(r, c) => FlowId::InRoom(r.clone(), c.relation.event_id.clone()), + Self::Room(r, c) => FlowId::InRoom(r.clone(), c.relates_to.event_id.clone()), } } @@ -651,94 +692,84 @@ impl From<(RoomId, AnyMessageEventContent)> for OutgoingContent { } } -#[cfg(test)] use crate::{OutgoingRequest, OutgoingVerificationRequest, RoomMessageRequest, ToDeviceRequest}; -#[cfg(test)] -impl From for OutgoingContent { - fn from(request: OutgoingVerificationRequest) -> Self { +impl TryFrom for OutgoingContent { + type Error = String; + + fn try_from(request: OutgoingVerificationRequest) -> Result { match request { - OutgoingVerificationRequest::ToDevice(r) => Self::try_from(r).unwrap(), - OutgoingVerificationRequest::InRoom(r) => Self::from(r), + OutgoingVerificationRequest::ToDevice(r) => Self::try_from(r), + OutgoingVerificationRequest::InRoom(r) => Ok(Self::from(r)), } } } -#[cfg(test)] impl From for OutgoingContent { fn from(value: RoomMessageRequest) -> Self { (value.room_id, value.content).into() } } -#[cfg(test)] impl TryFrom for OutgoingContent { - type Error = (); + type Error = String; - fn try_from(value: ToDeviceRequest) -> Result { + fn try_from(request: ToDeviceRequest) -> Result { use ruma::events::EventType; use serde_json::Value; let json: Value = serde_json::from_str( - value + request .messages .values() .next() - .and_then(|m| m.values().next().map(|j| j.get())) - .ok_or(())?, + .and_then(|m| m.values().next()) + .map(|c| c.json().get()) + .ok_or_else(|| "Content is missing from the request".to_owned())?, ) - .map_err(|_| ())?; + .map_err(|e| e.to_string())?; - match value.event_type { - EventType::KeyVerificationRequest => { - Ok(AnyToDeviceEventContent::KeyVerificationRequest( - serde_json::from_value(json).map_err(|_| ())?, - ) - .into()) - } - EventType::KeyVerificationReady => Ok(AnyToDeviceEventContent::KeyVerificationReady( - serde_json::from_value(json).map_err(|_| ())?, - ) - .into()), - EventType::KeyVerificationDone => Ok(AnyToDeviceEventContent::KeyVerificationDone( - serde_json::from_value(json).map_err(|_| ())?, - ) - .into()), - EventType::KeyVerificationStart => Ok(AnyToDeviceEventContent::KeyVerificationStart( - serde_json::from_value(json).map_err(|_| ())?, - ) - .into()), - EventType::KeyVerificationKey => Ok(AnyToDeviceEventContent::KeyVerificationKey( - serde_json::from_value(json).map_err(|_| ())?, - ) - .into()), - EventType::KeyVerificationAccept => Ok(AnyToDeviceEventContent::KeyVerificationAccept( - serde_json::from_value(json).map_err(|_| ())?, - ) - .into()), - EventType::KeyVerificationMac => Ok(AnyToDeviceEventContent::KeyVerificationMac( - serde_json::from_value(json).map_err(|_| ())?, - ) - .into()), - EventType::KeyVerificationCancel => Ok(AnyToDeviceEventContent::KeyVerificationCancel( - serde_json::from_value(json).map_err(|_| ())?, - ) - .into()), - _ => Err(()), - } + let content = match request.event_type { + EventType::KeyVerificationStart => AnyToDeviceEventContent::KeyVerificationStart( + serde_json::from_value(json).map_err(|e| e.to_string())?, + ), + EventType::KeyVerificationKey => AnyToDeviceEventContent::KeyVerificationKey( + serde_json::from_value(json).map_err(|e| e.to_string())?, + ), + EventType::KeyVerificationAccept => AnyToDeviceEventContent::KeyVerificationAccept( + serde_json::from_value(json).map_err(|e| e.to_string())?, + ), + EventType::KeyVerificationMac => AnyToDeviceEventContent::KeyVerificationMac( + serde_json::from_value(json).map_err(|e| e.to_string())?, + ), + EventType::KeyVerificationCancel => AnyToDeviceEventContent::KeyVerificationCancel( + serde_json::from_value(json).map_err(|e| e.to_string())?, + ), + EventType::KeyVerificationReady => AnyToDeviceEventContent::KeyVerificationReady( + serde_json::from_value(json).map_err(|e| e.to_string())?, + ), + EventType::KeyVerificationDone => AnyToDeviceEventContent::KeyVerificationDone( + serde_json::from_value(json).map_err(|e| e.to_string())?, + ), + EventType::KeyVerificationRequest => AnyToDeviceEventContent::KeyVerificationRequest( + serde_json::from_value(json).map_err(|e| e.to_string())?, + ), + e => return Err(format!("Unsupported event type {}", e)), + }; + + Ok(content.into()) } } -#[cfg(test)] impl TryFrom for OutgoingContent { - type Error = (); + type Error = String; - fn try_from(value: OutgoingRequest) -> Result { + fn try_from(value: OutgoingRequest) -> Result { match value.request() { - crate::OutgoingRequests::KeysUpload(_) => Err(()), - crate::OutgoingRequests::KeysQuery(_) => Err(()), + crate::OutgoingRequests::KeysUpload(_) => Err("Invalid request type".to_owned()), + crate::OutgoingRequests::KeysQuery(_) => Err("Invalid request type".to_owned()), crate::OutgoingRequests::ToDeviceRequest(r) => Self::try_from(r.clone()), - crate::OutgoingRequests::SignatureUpload(_) => Err(()), + crate::OutgoingRequests::SignatureUpload(_) => Err("Invalid request type".to_owned()), crate::OutgoingRequests::RoomMessage(r) => Ok(Self::from(r.clone())), } } diff --git a/matrix_sdk_crypto/src/verification/machine.rs b/matrix_sdk_crypto/src/verification/machine.rs index 371c78de..fbab484f 100644 --- a/matrix_sdk_crypto/src/verification/machine.rs +++ b/matrix_sdk_crypto/src/verification/machine.rs @@ -12,25 +12,36 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::{convert::TryFrom, sync::Arc}; +use std::{ + convert::{TryFrom, TryInto}, + sync::Arc, +}; use dashmap::DashMap; use matrix_sdk_common::{locks::Mutex, uuid::Uuid}; -use ruma::{DeviceId, UserId}; -use tracing::{info, warn}; +use ruma::{ + events::{ + key::verification::VerificationMethod, AnyToDeviceEvent, AnyToDeviceEventContent, + ToDeviceEvent, + }, + serde::Raw, + DeviceId, DeviceIdBox, EventId, MilliSecondsSinceUnixEpoch, RoomId, UserId, +}; +use tracing::{info, trace, warn}; use super::{ cache::VerificationCache, event_enums::{AnyEvent, AnyVerificationContent, OutgoingContent}, requests::VerificationRequest, - sas::{content_to_request, Sas}, - FlowId, VerificationResult, + sas::Sas, + FlowId, Verification, VerificationResult, }; use crate::{ olm::PrivateCrossSigningIdentity, requests::OutgoingRequest, store::{CryptoStore, CryptoStoreError}, - OutgoingVerificationRequest, ReadOnlyAccount, ReadOnlyDevice, RoomMessageRequest, + OutgoingVerificationRequest, ReadOnlyAccount, ReadOnlyDevice, ReadOnlyUserIdentity, + RoomMessageRequest, ToDeviceRequest, }; #[derive(Clone, Debug)] @@ -39,7 +50,7 @@ pub struct VerificationMachine { private_identity: Arc>, pub(crate) store: Arc, verifications: VerificationCache, - requests: Arc>, + requests: Arc>>, } impl VerificationMachine { @@ -57,6 +68,65 @@ impl VerificationMachine { } } + pub(crate) fn own_user_id(&self) -> &UserId { + self.account.user_id() + } + + pub(crate) fn own_device_id(&self) -> &DeviceId { + self.account.device_id() + } + + pub(crate) async fn request_to_device_verification( + &self, + user_id: &UserId, + recipient_devices: Vec, + methods: Option>, + ) -> (VerificationRequest, OutgoingVerificationRequest) { + let flow_id = FlowId::from(Uuid::new_v4().to_string()); + + let verification = VerificationRequest::new( + self.verifications.clone(), + self.account.clone(), + self.private_identity.lock().await.clone(), + self.store.clone(), + flow_id, + user_id, + recipient_devices, + methods, + ); + + self.insert_request(verification.clone()); + + let request = verification.request_to_device(); + + (verification, request.into()) + } + + pub async fn request_verification( + &self, + identity: &ReadOnlyUserIdentity, + room_id: &RoomId, + request_event_id: &EventId, + methods: Option>, + ) -> VerificationRequest { + let flow_id = FlowId::InRoom(room_id.to_owned(), request_event_id.to_owned()); + + let request = VerificationRequest::new( + self.verifications.clone(), + self.account.clone(), + self.private_identity.lock().await.clone(), + self.store.clone(), + flow_id, + identity.user_id(), + vec![], + methods, + ); + + self.insert_request(request.clone()); + + request + } + pub async fn start_sas( &self, device: ReadOnlyDevice, @@ -71,6 +141,8 @@ impl VerificationMachine { self.store.clone(), identity, None, + true, + None, ); let request = match content { @@ -79,7 +151,7 @@ impl VerificationMachine { } OutgoingContent::ToDevice(c) => { let request = - content_to_request(device.user_id(), device.device_id().to_owned(), c); + ToDeviceRequest::new(device.user_id(), device.device_id().to_owned(), c); self.verifications.insert_sas(sas.clone()); @@ -90,12 +162,60 @@ impl VerificationMachine { Ok((sas, request)) } - pub fn get_request(&self, flow_id: impl AsRef) -> Option { - self.requests.get(flow_id.as_ref()).map(|s| s.clone()) + pub fn get_request( + &self, + user_id: &UserId, + flow_id: impl AsRef, + ) -> Option { + self.requests.get(user_id).and_then(|v| v.get(flow_id.as_ref()).map(|s| s.clone())) } - pub fn get_sas(&self, transaction_id: &str) -> Option { - self.verifications.get_sas(transaction_id) + pub fn get_requests(&self, user_id: &UserId) -> Vec { + self.requests + .get(user_id) + .map(|v| v.iter().map(|i| i.value().clone()).collect()) + .unwrap_or_default() + } + + fn insert_request(&self, request: VerificationRequest) { + self.requests + .entry(request.other_user().to_owned()) + .or_insert_with(DashMap::new) + .insert(request.flow_id().as_str().to_owned(), request); + } + + pub fn get_verification(&self, user_id: &UserId, flow_id: &str) -> Option { + self.verifications.get(user_id, flow_id) + } + + pub fn get_sas(&self, user_id: &UserId, flow_id: &str) -> Option { + self.verifications.get_sas(user_id, flow_id) + } + + #[cfg(not(target_arch = "wasm32"))] + fn is_timestamp_valid(timestamp: &MilliSecondsSinceUnixEpoch) -> bool { + use ruma::{uint, UInt}; + + // The event should be ignored if the event is older than 10 minutes + let old_timestamp_threshold: UInt = uint!(600); + // The event should be ignored if the event is 5 minutes or more into the + // future. + let timestamp_threshold: UInt = uint!(300); + + let timestamp = timestamp.as_secs(); + let now = MilliSecondsSinceUnixEpoch::now().as_secs(); + + !(now.saturating_sub(timestamp) > old_timestamp_threshold + || timestamp.saturating_sub(now) > timestamp_threshold) + } + + #[cfg(target_arch = "wasm32")] + fn is_timestamp_valid(timestamp: &MilliSecondsSinceUnixEpoch) -> bool { + // TODO the non-wasm method with the same name uses + // `MilliSecondsSinceUnixEpoch::now()` which internally uses + // `SystemTime::now()` this panics under WASM, thus we're returning here + // true for now. + true } fn queue_up_content( @@ -115,12 +235,40 @@ impl VerificationMachine { self.verifications.outgoing_requests() } - pub fn garbage_collect(&self) { - self.requests.retain(|_, r| !(r.is_done() || r.is_cancelled())); + pub fn garbage_collect(&self) -> Vec> { + let mut events = vec![]; - for request in self.verifications.garbage_collect() { - self.verifications.add_request(request) + for user_verification in self.requests.iter() { + user_verification.retain(|_, v| !(v.is_done() || v.is_cancelled())); } + self.requests.retain(|_, v| !v.is_empty()); + + let mut requests: Vec = self + .requests + .iter() + .flat_map(|v| { + let requests: Vec = + v.value().iter().filter_map(|v| v.cancel_if_timed_out()).collect(); + requests + }) + .collect(); + + requests.extend(self.verifications.garbage_collect().into_iter()); + + for request in requests { + if let Ok(OutgoingContent::ToDevice(AnyToDeviceEventContent::KeyVerificationCancel( + content, + ))) = request.clone().try_into() + { + let event = ToDeviceEvent { content, sender: self.account.user_id().to_owned() }; + + events.push(AnyToDeviceEvent::KeyVerificationCancel(event).into()); + } + + self.verifications.add_verification_request(request) + } + + events } async fn mark_sas_as_done( @@ -175,6 +323,14 @@ impl VerificationMachine { ); }; + let event_sent_from_us = |event: &AnyEvent<'_>, from_device: &DeviceId| { + if event.sender() == self.account.user_id() { + from_device == self.account.device_id() || event.is_room_event() + } else { + false + } + }; + if let Some(content) = event.verification_content() { match &content { AnyVerificationContent::Request(r) => { @@ -184,40 +340,71 @@ impl VerificationMachine { "Received a new verification request", ); - let request = VerificationRequest::from_request( - self.verifications.clone(), - self.account.clone(), - self.private_identity.lock().await.clone(), - self.store.clone(), - event.sender(), - flow_id, - r, - ); + if let Some(timestamp) = event.timestamp() { + if Self::is_timestamp_valid(timestamp) { + if !event_sent_from_us(&event, r.from_device()) { + let request = VerificationRequest::from_request( + self.verifications.clone(), + self.account.clone(), + self.private_identity.lock().await.clone(), + self.store.clone(), + event.sender(), + flow_id, + r, + ); - self.requests.insert(request.flow_id().as_str().to_owned(), request); + self.insert_request(request); + } else { + trace!( + sender = event.sender().as_str(), + from_device = r.from_device().as_str(), + "The received verification request was sent by us, ignoring it", + ); + } + } else { + trace!( + sender = event.sender().as_str(), + from_device = r.from_device().as_str(), + timestamp =? timestamp, + "The received verification request was too old or too far into the future", + ); + } + } else { + warn!( + sender = event.sender().as_str(), + from_device = r.from_device().as_str(), + "The key verification request didn't contain a valid timestamp" + ); + } } AnyVerificationContent::Cancel(c) => { - if let Some(verification) = self.get_request(flow_id.as_str()) { + if let Some(verification) = self.get_request(event.sender(), flow_id.as_str()) { verification.receive_cancel(event.sender(), c); } - if let Some(sas) = self.verifications.get_sas(flow_id.as_str()) { - // This won't produce an outgoing content - let _ = sas.receive_any_event(event.sender(), &content); + if let Some(verification) = + self.get_verification(event.sender(), flow_id.as_str()) + { + match verification { + Verification::SasV1(sas) => { + // This won't produce an outgoing content + let _ = sas.receive_any_event(event.sender(), &content); + } + Verification::QrV1(qr) => qr.receive_cancel(event.sender(), c), + } } } AnyVerificationContent::Ready(c) => { - if let Some(request) = self.requests.get(flow_id.as_str()) { + if let Some(request) = self.get_request(event.sender(), flow_id.as_str()) { if request.flow_id() == &flow_id { - // TODO remove this unwrap. - request.receive_ready(event.sender(), c).unwrap(); + request.receive_ready(event.sender(), c); } else { flow_id_mismatch(); } } } AnyVerificationContent::Start(c) => { - if let Some(request) = self.requests.get(flow_id.as_str()) { + if let Some(request) = self.get_request(event.sender(), flow_id.as_str()) { if request.flow_id() == &flow_id { request.receive_start(event.sender(), c).await? } else { @@ -240,6 +427,7 @@ impl VerificationMachine { private_identity, device, identity, + None, false, ) { Ok(sas) => { @@ -255,7 +443,7 @@ impl VerificationMachine { } } AnyVerificationContent::Accept(_) | AnyVerificationContent::Key(_) => { - if let Some(sas) = self.verifications.get_sas(flow_id.as_str()) { + if let Some(sas) = self.get_sas(event.sender(), flow_id.as_str()) { if sas.flow_id() == &flow_id { if let Some(content) = sas.receive_any_event(event.sender(), &content) { self.queue_up_content( @@ -270,7 +458,7 @@ impl VerificationMachine { } } AnyVerificationContent::Mac(_) => { - if let Some(s) = self.verifications.get_sas(flow_id.as_str()) { + if let Some(s) = self.get_sas(event.sender(), flow_id.as_str()) { if s.flow_id() == &flow_id { let content = s.receive_any_event(event.sender(), &content); @@ -283,16 +471,30 @@ impl VerificationMachine { } } AnyVerificationContent::Done(c) => { - if let Some(verification) = self.get_request(flow_id.as_str()) { + if let Some(verification) = self.get_request(event.sender(), flow_id.as_str()) { verification.receive_done(event.sender(), c); } - if let Some(s) = self.verifications.get_sas(flow_id.as_str()) { - let content = s.receive_any_event(event.sender(), &content); + match self.get_verification(event.sender(), flow_id.as_str()) { + Some(Verification::SasV1(sas)) => { + let content = sas.receive_any_event(event.sender(), &content); - if s.is_done() { - self.mark_sas_as_done(s, content).await?; + if sas.is_done() { + self.mark_sas_as_done(sas, content).await?; + } } + Some(Verification::QrV1(qr)) => { + let (cancellation, request) = qr.receive_done(&c).await?; + + if let Some(c) = cancellation { + self.verifications.add_request(c.into()) + } + + if let Some(s) = request { + self.verifications.add_request(s.into()) + } + } + None => (), } } } @@ -363,6 +565,8 @@ mod test { bob_store, None, None, + true, + None, ); machine @@ -385,7 +589,7 @@ mod test { async fn full_flow() { let (alice_machine, bob) = setup_verification_machine().await; - let alice = alice_machine.get_sas(bob.flow_id().as_str()).unwrap(); + let alice = alice_machine.get_sas(bob.user_id(), bob.flow_id().as_str()).unwrap(); let request = alice.accept().unwrap(); @@ -431,7 +635,7 @@ mod test { #[tokio::test] async fn timing_out() { let (alice_machine, bob) = setup_verification_machine().await; - let alice = alice_machine.get_sas(bob.flow_id().as_str()).unwrap(); + let alice = alice_machine.get_sas(bob.user_id(), bob.flow_id().as_str()).unwrap(); assert!(!alice.timed_out()); assert!(alice_machine.verifications.outgoing_requests().is_empty()); diff --git a/matrix_sdk_crypto/src/verification/mod.rs b/matrix_sdk_crypto/src/verification/mod.rs index 3335ec72..20ed6105 100644 --- a/matrix_sdk_crypto/src/verification/mod.rs +++ b/matrix_sdk_crypto/src/verification/mod.rs @@ -15,6 +15,7 @@ mod cache; mod event_enums; mod machine; +mod qrcode; mod requests; mod sas; @@ -22,6 +23,7 @@ use std::sync::Arc; use event_enums::OutgoingContent; pub use machine::VerificationMachine; +pub use qrcode::QrVerification; pub use requests::VerificationRequest; use ruma::{ api::client::r0::keys::upload_signatures::Request as SignatureUploadRequest, @@ -41,25 +43,83 @@ use tracing::{error, info, trace, warn}; use crate::{ error::SignatureError, olm::PrivateCrossSigningIdentity, - store::{Changes, CryptoStore, DeviceChanges}, - CryptoStoreError, LocalTrust, ReadOnlyDevice, UserIdentities, + store::{Changes, CryptoStore}, + CryptoStoreError, LocalTrust, ReadOnlyDevice, ReadOnlyUserIdentities, }; +/// An enum over the different verification types the SDK supports. #[derive(Clone, Debug)] pub enum Verification { + /// The `m.sas.v1` verification variant. SasV1(Sas), + /// The `m.qr_code.*.v1` verification variant. + QrV1(QrVerification), } impl Verification { - pub fn is_done(&self) -> bool { - match self { - Verification::SasV1(s) => s.is_done(), + /// Try to deconstruct this verification enum into a SAS verification. + pub fn sas_v1(self) -> Option { + if let Verification::SasV1(sas) = self { + Some(sas) + } else { + None } } + /// Try to deconstruct this verification enum into a QR code verification. + pub fn qr_v1(self) -> Option { + if let Verification::QrV1(qr) = self { + Some(qr) + } else { + None + } + } + + /// Has this verification finished. + pub fn is_done(&self) -> bool { + match self { + Verification::SasV1(s) => s.is_done(), + Verification::QrV1(qr) => qr.is_done(), + } + } + + /// Get the ID that uniquely identifies this verification flow. + pub fn flow_id(&self) -> &str { + match self { + Verification::SasV1(s) => s.flow_id().as_str(), + Verification::QrV1(qr) => qr.flow_id().as_str(), + } + } + + /// Has the verification been cancelled. pub fn is_cancelled(&self) -> bool { match self { Verification::SasV1(s) => s.is_cancelled(), + Verification::QrV1(qr) => qr.is_cancelled(), + } + } + + /// Get our own user id that is participating in this verification. + pub fn user_id(&self) -> &UserId { + match self { + Verification::SasV1(v) => v.user_id(), + Verification::QrV1(v) => v.user_id(), + } + } + + /// Get the other user id that is participating in this verification. + pub fn other_user(&self) -> &UserId { + match self { + Verification::SasV1(s) => s.other_user_id(), + Verification::QrV1(qr) => qr.other_user_id(), + } + } + + /// Is this a verification verifying a device that belongs to us. + pub fn is_self_verification(&self) -> bool { + match self { + Verification::SasV1(v) => v.is_self_verification(), + Verification::QrV1(v) => v.is_self_verification(), } } } @@ -70,6 +130,12 @@ impl From for Verification { } } +impl From for Verification { + fn from(qr: QrVerification) -> Self { + Self::QrV1(qr) + } +} + /// The verification state indicating that the verification finished /// successfully. /// @@ -78,7 +144,7 @@ impl From for Verification { #[derive(Clone, Debug)] pub struct Done { verified_devices: Arc<[ReadOnlyDevice]>, - verified_master_keys: Arc<[UserIdentities]>, + verified_master_keys: Arc<[ReadOnlyUserIdentities]>, } impl Done { @@ -99,14 +165,47 @@ impl Done { } } +/// Information about the cancellation of a verification request or verification +/// flow. +#[derive(Clone, Debug)] +pub struct CancelInfo { + cancelled_by_us: bool, + cancel_code: CancelCode, + reason: &'static str, +} + +impl CancelInfo { + /// Get the human readable reason of the cancellation. + pub fn reason(&self) -> &'static str { + &self.reason + } + + /// Get the `CancelCode` that cancelled this verification. + pub fn cancel_code(&self) -> &CancelCode { + &self.cancel_code + } + + /// Was the verification cancelled by us? + pub fn cancelled_by_us(&self) -> bool { + self.cancelled_by_us + } +} + +impl From for CancelInfo { + fn from(c: Cancelled) -> Self { + Self { cancelled_by_us: c.cancelled_by_us, cancel_code: c.cancel_code, reason: c.reason } + } +} + #[derive(Clone, Debug)] pub struct Cancelled { + cancelled_by_us: bool, cancel_code: CancelCode, reason: &'static str, } impl Cancelled { - fn new(code: CancelCode) -> Self { + fn new(cancelled_by_us: bool, code: CancelCode) -> Self { let reason = match code { CancelCode::Accepted => { "A m.key.verification.request was accepted by a different device." @@ -126,7 +225,7 @@ impl Cancelled { _ => "Unknown cancel reason", }; - Self { cancel_code: code, reason } + Self { cancelled_by_us, cancel_code: code, reason } } pub fn as_content(&self, flow_id: &FlowId) -> OutgoingContent { @@ -210,14 +309,22 @@ pub struct IdentitiesBeingVerified { private_identity: PrivateCrossSigningIdentity, store: Arc, device_being_verified: ReadOnlyDevice, - identity_being_verified: Option, + identity_being_verified: Option, } impl IdentitiesBeingVerified { + async fn can_sign_devices(&self) -> bool { + self.private_identity.can_sign_devices().await + } + fn user_id(&self) -> &UserId { self.private_identity.user_id() } + fn is_self_verification(&self) -> bool { + self.user_id() == self.other_user_id() + } + fn other_user_id(&self) -> &UserId { self.device_being_verified.user_id() } @@ -233,11 +340,20 @@ impl IdentitiesBeingVerified { pub async fn mark_as_done( &self, verified_devices: Option<&[ReadOnlyDevice]>, - verified_identities: Option<&[UserIdentities]>, + verified_identities: Option<&[ReadOnlyUserIdentities]>, ) -> Result { - if let Some(device) = self.mark_device_as_verified(verified_devices).await? { - let identity = self.mark_identity_as_verified(verified_identities).await?; + let device = self.mark_device_as_verified(verified_devices).await?; + let identity = self.mark_identity_as_verified(verified_identities).await?; + if device.is_none() && identity.is_none() { + // Something wen't wrong if nothing was verified, we use key + // mismatch here, since it's the closest to nothing was verified + return Ok(VerificationResult::Cancel(CancelCode::KeyMismatch)); + } + + let mut changes = Changes::default(); + + let signature_request = if let Some(device) = device { // We only sign devices of our own user here. let signature_request = if device.user_id() == self.user_id() { match self.private_identity.sign_device(&device).await { @@ -266,83 +382,79 @@ impl IdentitiesBeingVerified { None }; - let mut changes = Changes { - devices: DeviceChanges { changed: vec![device], ..Default::default() }, - ..Default::default() - }; + changes.devices.changed.push(device); + signature_request + } else { + None + }; - let identity_signature_request = if let Some(i) = identity { - // We only sign other users here. - let request = if let Some(i) = i.other() { - // Signing can fail if the user signing key is missing. - match self.private_identity.sign_user(i).await { - Ok(r) => Some(r), - Err(SignatureError::MissingSigningKey) => { - warn!( - "Can't sign the public cross signing keys for {}, \ - no private user signing key found", - i.user_id() - ); - None - } - Err(e) => { - error!( - "Error signing the public cross signing keys for {} {:?}", - i.user_id(), - e - ); - None - } + let identity_signature_request = if let Some(i) = identity { + // We only sign other users here. + let request = if let Some(i) = i.other() { + // Signing can fail if the user signing key is missing. + match self.private_identity.sign_user(i).await { + Ok(r) => Some(r), + Err(SignatureError::MissingSigningKey) => { + warn!( + "Can't sign the public cross signing keys for {}, \ + no private user signing key found", + i.user_id() + ); + None } - } else { - None - }; - - changes.identities.changed.push(i); - - request + Err(e) => { + error!( + "Error signing the public cross signing keys for {} {:?}", + i.user_id(), + e + ); + None + } + } } else { None }; - // If there are two signature upload requests, merge them. Otherwise - // use the one we have or None. - // - // Realistically at most one request will be used but let's make - // this future proof. - let merged_request = if let Some(mut r) = signature_request { - if let Some(user_request) = identity_signature_request { - r.signed_keys.extend(user_request.signed_keys); - Some(r) - } else { - Some(r) - } - } else { - identity_signature_request - }; - - // TODO store the signature upload request as well. - self.store.save_changes(changes).await?; - Ok(merged_request - .map(VerificationResult::SignatureUpload) - .unwrap_or(VerificationResult::Ok)) + changes.identities.changed.push(i); + request } else { - Ok(VerificationResult::Cancel(CancelCode::UserMismatch)) - } + None + }; + + // If there are two signature upload requests, merge them. Otherwise + // use the one we have or None. + // + // Realistically at most one request will be used but let's make + // this future proof. + let merged_request = if let Some(mut r) = signature_request { + if let Some(user_request) = identity_signature_request { + r.signed_keys.extend(user_request.signed_keys); + Some(r) + } else { + Some(r) + } + } else { + identity_signature_request + }; + + // TODO store the signature upload request as well. + self.store.save_changes(changes).await?; + + Ok(merged_request + .map(VerificationResult::SignatureUpload) + .unwrap_or(VerificationResult::Ok)) } async fn mark_identity_as_verified( &self, - verified_identities: Option<&[UserIdentities]>, - ) -> Result, CryptoStoreError> { + verified_identities: Option<&[ReadOnlyUserIdentities]>, + ) -> Result, CryptoStoreError> { // If there wasn't an identity available during the verification flow // return early as there's nothing to do. if self.identity_being_verified.is_none() { return Ok(None); } - // TODO signal an error, e.g. when the identity got deleted so we don't - // verify/save the device either. let identity = self.store.get_user_identity(self.other_user_id()).await?; if let Some(identity) = identity { @@ -357,7 +469,7 @@ impl IdentitiesBeingVerified { "Marking the user identity of as verified." ); - if let UserIdentities::Own(i) = &identity { + if let ReadOnlyUserIdentities::Own(i) = &identity { i.mark_as_verified(); } @@ -445,11 +557,12 @@ impl IdentitiesBeingVerified { #[cfg(test)] pub(crate) mod test { + use std::convert::TryInto; + use ruma::{ - events::{AnyToDeviceEvent, AnyToDeviceEventContent, EventType, ToDeviceEvent}, + events::{AnyToDeviceEvent, AnyToDeviceEventContent, ToDeviceEvent}, UserId, }; - use serde_json::Value; use super::event_enums::OutgoingContent; use crate::{ @@ -461,7 +574,8 @@ pub(crate) mod test { sender: &UserId, request: &OutgoingVerificationRequest, ) -> AnyToDeviceEvent { - let content = get_content_from_request(request); + let content = + request.to_owned().try_into().expect("Can't fetch content out of the request"); wrap_any_to_device_content(sender, content) } @@ -510,36 +624,4 @@ pub(crate) mod test { _ => unreachable!(), } } - - pub(crate) fn get_content_from_request( - request: &OutgoingVerificationRequest, - ) -> OutgoingContent { - let request = - if let OutgoingVerificationRequest::ToDevice(r) = request { r } else { unreachable!() }; - - let json: Value = serde_json::from_str( - request.messages.values().next().unwrap().values().next().unwrap().get(), - ) - .unwrap(); - - match request.event_type { - EventType::KeyVerificationStart => { - AnyToDeviceEventContent::KeyVerificationStart(serde_json::from_value(json).unwrap()) - } - EventType::KeyVerificationKey => { - AnyToDeviceEventContent::KeyVerificationKey(serde_json::from_value(json).unwrap()) - } - EventType::KeyVerificationAccept => AnyToDeviceEventContent::KeyVerificationAccept( - serde_json::from_value(json).unwrap(), - ), - EventType::KeyVerificationMac => { - AnyToDeviceEventContent::KeyVerificationMac(serde_json::from_value(json).unwrap()) - } - EventType::KeyVerificationCancel => AnyToDeviceEventContent::KeyVerificationCancel( - serde_json::from_value(json).unwrap(), - ), - _ => unreachable!(), - } - .into() - } } diff --git a/matrix_sdk_crypto/src/verification/qrcode.rs b/matrix_sdk_crypto/src/verification/qrcode.rs new file mode 100644 index 00000000..a645a8e8 --- /dev/null +++ b/matrix_sdk_crypto/src/verification/qrcode.rs @@ -0,0 +1,1001 @@ +// Copyright 2021 The Matrix.org Foundation C.I.C. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use std::sync::{Arc, Mutex}; + +use matrix_qrcode::{ + qrcode::QrCode, EncodingError, QrVerificationData, SelfVerificationData, + SelfVerificationNoMasterKey, VerificationData, +}; +use matrix_sdk_common::uuid::Uuid; +use ruma::{ + api::client::r0::keys::upload_signatures::Request as SignatureUploadRequest, + events::{ + key::verification::{ + cancel::CancelCode, + done::{DoneEventContent, DoneToDeviceEventContent}, + start::{ + self, ReciprocateV1Content, StartEventContent, StartMethod, + StartToDeviceEventContent, + }, + Relation, + }, + AnyMessageEventContent, AnyToDeviceEventContent, + }, + DeviceId, DeviceIdBox, DeviceKeyAlgorithm, RoomId, UserId, +}; +use thiserror::Error; +use tracing::trace; + +use super::{ + event_enums::{CancelContent, DoneContent, OutgoingContent, OwnedStartContent, StartContent}, + requests::RequestHandle, + CancelInfo, Cancelled, Done, FlowId, IdentitiesBeingVerified, VerificationResult, +}; +use crate::{ + olm::{PrivateCrossSigningIdentity, ReadOnlyAccount}, + store::CryptoStore, + CryptoStoreError, OutgoingVerificationRequest, ReadOnlyDevice, ReadOnlyUserIdentities, + RoomMessageRequest, ToDeviceRequest, +}; + +const SECRET_SIZE: usize = 16; + +/// An error for the different failure modes that can happen during the +/// validation of a scanned QR code. +#[derive(Debug, Error)] +pub enum ScanError { + /// An IO error inside the crypto store happened during the validation of + /// the QR code scan. + #[error(transparent)] + Store(#[from] CryptoStoreError), + /// A key mismatch happened during the validation of the QR code scan. + #[error("The keys that are being verified didn't match (expected {expected}, found {found})")] + KeyMismatch { expected: String, found: String }, + /// One of the users that is participating in this verification doesn't have + /// a valid cross signing identity. + #[error("The user {0} is missing a valid cross signing identity")] + MissingCrossSigningIdentity(UserId), + /// The device of the user that is participating in this verification + /// doesn't have a valid device key. + #[error("The user's {0} device {1} is not E2E capable")] + MissingDeviceKeys(UserId, DeviceIdBox), + /// The ID uniquely identifying this verification flow didn't match to the + /// one that has been scanned. + #[error("The unique verification flow id did not match (expected {expected}, found {found})")] + FlowIdMismatch { expected: String, found: String }, +} + +/// An object controlling QR code style key verification flows. +#[derive(Clone)] +pub struct QrVerification { + flow_id: FlowId, + store: Arc, + inner: Arc, + state: Arc>, + identities: IdentitiesBeingVerified, + request_handle: Option, + we_started: bool, +} + +impl std::fmt::Debug for QrVerification { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("QrVerification") + .field("flow_id", &self.flow_id) + .field("inner", self.inner.as_ref()) + .field("state", &self.state.lock().unwrap()) + .finish() + } +} + +impl QrVerification { + /// Has the QR verification been scanned by the other side. + /// + /// When the verification object is in this state it's required that the + /// user confirms that the other side has scanned the QR code. + pub fn has_been_scanned(&self) -> bool { + matches!(&*self.state.lock().unwrap(), InnerState::Scanned(_)) + } + + /// Has the scanning of the QR code been confirmed by us. + pub fn has_been_confirmed(&self) -> bool { + matches!(&*self.state.lock().unwrap(), InnerState::Confirmed(_)) + } + + /// Get our own user id. + pub fn user_id(&self) -> &UserId { + self.identities.user_id() + } + + /// Get the user id of the other user that is participating in this + /// verification flow. + pub fn other_user_id(&self) -> &UserId { + self.identities.other_user_id() + } + + /// Get the device id of the other side. + pub fn other_device_id(&self) -> &DeviceId { + self.identities.other_device_id() + } + + /// Did we initiate the verification request + pub fn we_started(&self) -> bool { + self.we_started + } + + /// Get info about the cancellation if the verification flow has been + /// cancelled. + pub fn cancel_info(&self) -> Option { + if let InnerState::Cancelled(c) = &*self.state.lock().unwrap() { + Some(c.state.clone().into()) + } else { + None + } + } + + /// Has the verification flow completed. + pub fn is_done(&self) -> bool { + matches!(&*self.state.lock().unwrap(), InnerState::Done(_)) + } + + /// Has the verification flow been cancelled. + pub fn is_cancelled(&self) -> bool { + matches!(&*self.state.lock().unwrap(), InnerState::Cancelled(_)) + } + + /// Is this a verification that is veryfying one of our own devices + pub fn is_self_verification(&self) -> bool { + self.identities.is_self_verification() + } + + /// Have we successfully scanned the QR code and are able to send a + /// reciprocation event. + pub fn reciprocated(&self) -> bool { + matches!(&*self.state.lock().unwrap(), InnerState::Reciprocated(_)) + } + + /// Get the unique ID that identifies this QR code verification flow. + pub fn flow_id(&self) -> &FlowId { + &self.flow_id + } + + /// Get the room id if the verification is happening inside a room. + pub fn room_id(&self) -> Option<&RoomId> { + match self.flow_id() { + FlowId::ToDevice(_) => None, + FlowId::InRoom(r, _) => Some(r), + } + } + + /// Generate a QR code object that is representing this verification flow. + /// + /// The `QrCode` can then be rendered as an image or as an unicode string. + /// + /// The [`to_bytes()`](#method.to_bytes) method can be used to instead + /// output the raw bytes that should be encoded as a QR code. + pub fn to_qr_code(&self) -> Result { + self.inner.to_qr_code() + } + + /// Generate a the raw bytes that should be encoded as a QR code is + /// representing this verification flow. + /// + /// The [`to_qr_code()`](#method.to_qr_code) method can be used to instead + /// output a `QrCode` object that can be rendered. + pub fn to_bytes(&self) -> Result, EncodingError> { + self.inner.to_bytes() + } + + /// Cancel the verification flow. + pub fn cancel(&self) -> Option { + self.cancel_with_code(CancelCode::User) + } + + /// Cancel the verification. + /// + /// This cancels the verification with given `CancelCode`. + /// + /// **Note**: This method should generally not be used, the [`cancel()`] + /// method should be preferred. The SDK will automatically cancel with the + /// approprate cancel code, user initiated cancellations should only cancel + /// with the `CancelCode::User` + /// + /// Returns None if the `Sas` object is already in a canceled state, + /// otherwise it returns a request that needs to be sent out. + /// + /// [`cancel()`]: #method.cancel + pub fn cancel_with_code(&self, code: CancelCode) -> Option { + let mut state = self.state.lock().unwrap(); + + if let Some(request) = &self.request_handle { + request.cancel_with_code(&code); + } + + let new_state = QrState::::new(true, code); + let content = new_state.as_content(self.flow_id()); + + match &*state { + InnerState::Confirmed(_) + | InnerState::Created(_) + | InnerState::Scanned(_) + | InnerState::Reciprocated(_) + | InnerState::Done(_) => { + *state = InnerState::Cancelled(new_state); + Some(self.content_to_request(content)) + } + InnerState::Cancelled(_) => None, + } + } + + /// Notify the other side that we have successfully scanned the QR code and + /// that the QR verification flow can start. + /// + /// This will return some `OutgoingContent` if the object is in the correct + /// state to start the verification flow, otherwise `None`. + pub fn reciprocate(&self) -> Option { + match &*self.state.lock().unwrap() { + InnerState::Reciprocated(s) => { + Some(self.content_to_request(s.as_content(self.flow_id()))) + } + InnerState::Created(_) + | InnerState::Scanned(_) + | InnerState::Confirmed(_) + | InnerState::Done(_) + | InnerState::Cancelled(_) => None, + } + } + + /// Confirm that the other side has scanned our QR code. + pub fn confirm_scanning(&self) -> Option { + let mut state = self.state.lock().unwrap(); + + match &*state { + InnerState::Scanned(s) => { + let new_state = s.clone().confirm_scanning(); + let content = new_state.as_content(&self.flow_id); + *state = InnerState::Confirmed(new_state); + + Some(self.content_to_request(content)) + } + InnerState::Created(_) + | InnerState::Cancelled(_) + | InnerState::Confirmed(_) + | InnerState::Reciprocated(_) + | InnerState::Done(_) => None, + } + } + + fn content_to_request(&self, content: OutgoingContent) -> OutgoingVerificationRequest { + match content { + OutgoingContent::Room(room_id, content) => { + RoomMessageRequest { room_id, txn_id: Uuid::new_v4(), content }.into() + } + OutgoingContent::ToDevice(c) => ToDeviceRequest::new( + self.identities.other_user_id(), + self.identities.other_device_id().to_owned(), + c, + ) + .into(), + } + } + + async fn mark_as_done( + &self, + new_state: QrState, + ) -> Result< + (Option, Option), + CryptoStoreError, + > { + let (devices, identities) = new_state.verified_identities(); + + let mut new_state = InnerState::Done(new_state); + + let (content, request) = + match self.identities.mark_as_done(Some(&devices), Some(&identities)).await? { + VerificationResult::Ok => (None, None), + VerificationResult::Cancel(c) => { + let canceled = QrState::::new(false, c); + let content = canceled.as_content(self.flow_id()); + new_state = InnerState::Cancelled(canceled); + (Some(content), None) + } + VerificationResult::SignatureUpload(s) => (None, Some(s)), + }; + + *self.state.lock().unwrap() = new_state; + + Ok((content.map(|c| self.content_to_request(c)), request)) + } + + pub(crate) async fn receive_done( + &self, + content: &DoneContent<'_>, + ) -> Result< + (Option, Option), + CryptoStoreError, + > { + let state = (*self.state.lock().unwrap()).clone(); + + Ok(match state { + InnerState::Confirmed(s) => { + let (verified_device, verified_identity) = match &*self.inner { + QrVerificationData::Verification(_) => { + (None, self.identities.identity_being_verified.as_ref()) + } + QrVerificationData::SelfVerification(_) => { + (Some(&self.identities.device_being_verified), None) + } + QrVerificationData::SelfVerificationNoMasterKey(_) => { + (None, self.identities.identity_being_verified.as_ref()) + } + }; + + let new_state = s.clone().into_done(content, verified_device, verified_identity); + self.mark_as_done(new_state).await? + } + InnerState::Reciprocated(s) => { + let (verified_device, verified_identity) = match &*self.inner { + QrVerificationData::Verification(_) => { + (None, self.identities.identity_being_verified.as_ref()) + } + QrVerificationData::SelfVerification(_) => { + (None, self.identities.identity_being_verified.as_ref()) + } + QrVerificationData::SelfVerificationNoMasterKey(_) => { + (Some(&self.identities.device_being_verified), None) + } + }; + + let new_state = s.clone().into_done(content, verified_device, verified_identity); + let content = Some(new_state.as_content(self.flow_id())); + let (cancel_content, request) = self.mark_as_done(new_state).await?; + + if cancel_content.is_some() { + (cancel_content, request) + } else { + (content.map(|c| self.content_to_request(c)), request) + } + } + InnerState::Created(_) + | InnerState::Scanned(_) + | InnerState::Done(_) + | InnerState::Cancelled(_) => (None, None), + }) + } + + pub(crate) fn receive_reciprocation( + &self, + content: &StartContent, + ) -> Option { + let mut state = self.state.lock().unwrap(); + + match &*state { + InnerState::Created(s) => match s.clone().receive_reciprocate(content) { + Ok(s) => { + *state = InnerState::Scanned(s); + None + } + Err(s) => { + let content = s.as_content(self.flow_id()); + *state = InnerState::Cancelled(s); + Some(self.content_to_request(content)) + } + }, + InnerState::Confirmed(_) + | InnerState::Scanned(_) + | InnerState::Reciprocated(_) + | InnerState::Done(_) + | InnerState::Cancelled(_) => None, + } + } + + pub(crate) fn receive_cancel(&self, sender: &UserId, content: &CancelContent<'_>) { + if sender == self.other_user_id() { + let mut state = self.state.lock().unwrap(); + + let new_state = match &*state { + InnerState::Created(s) => s.clone().into_cancelled(content), + InnerState::Scanned(s) => s.clone().into_cancelled(content), + InnerState::Confirmed(s) => s.clone().into_cancelled(content), + InnerState::Reciprocated(s) => s.clone().into_cancelled(content), + InnerState::Done(_) | InnerState::Cancelled(_) => return, + }; + + trace!( + sender = sender.as_str(), + code = content.cancel_code().as_str(), + "Cancelling a QR verification, other user has cancelled" + ); + + *state = InnerState::Cancelled(new_state); + } + } + + fn generate_secret() -> String { + let mut shared_secret = [0u8; SECRET_SIZE]; + getrandom::getrandom(&mut shared_secret) + .expect("Can't generate randomness for the shared secret"); + crate::utilities::encode(shared_secret) + } + + pub(crate) fn new_self( + store: Arc, + flow_id: FlowId, + own_master_key: String, + other_device_key: String, + identities: IdentitiesBeingVerified, + we_started: bool, + request_handle: Option, + ) -> Self { + let secret = Self::generate_secret(); + + let inner: QrVerificationData = SelfVerificationData::new( + flow_id.as_str().to_owned(), + own_master_key, + other_device_key, + secret, + ) + .into(); + + Self::new_helper(store, flow_id, inner, identities, we_started, request_handle) + } + + pub(crate) fn new_self_no_master( + account: ReadOnlyAccount, + store: Arc, + flow_id: FlowId, + own_master_key: String, + identities: IdentitiesBeingVerified, + we_started: bool, + request_handle: Option, + ) -> QrVerification { + let secret = Self::generate_secret(); + + let inner: QrVerificationData = SelfVerificationNoMasterKey::new( + flow_id.as_str().to_owned(), + account.identity_keys().ed25519().to_string(), + own_master_key, + secret, + ) + .into(); + + Self::new_helper(store, flow_id, inner, identities, we_started, request_handle) + } + + pub(crate) fn new_cross( + store: Arc, + flow_id: FlowId, + own_master_key: String, + other_master_key: String, + identities: IdentitiesBeingVerified, + we_started: bool, + request_handle: Option, + ) -> Self { + let secret = Self::generate_secret(); + + let event_id = if let FlowId::InRoom(_, e) = &flow_id { + e.to_owned() + } else { + panic!("A verification between users is only valid in a room"); + }; + + let inner: QrVerificationData = + VerificationData::new(event_id, own_master_key, other_master_key, secret).into(); + + Self::new_helper(store, flow_id, inner, identities, we_started, request_handle) + } + + #[allow(clippy::too_many_arguments)] + pub(crate) async fn from_scan( + store: Arc, + own_account: ReadOnlyAccount, + private_identity: PrivateCrossSigningIdentity, + other_user_id: UserId, + other_device_id: DeviceIdBox, + flow_id: FlowId, + qr_code: QrVerificationData, + we_started: bool, + request_handle: Option, + ) -> Result { + if flow_id.as_str() != qr_code.flow_id() { + return Err(ScanError::FlowIdMismatch { + expected: flow_id.as_str().to_owned(), + found: qr_code.flow_id().to_owned(), + }); + } + + let own_identity = + store.get_user_identity(own_account.user_id()).await?.ok_or_else(|| { + ScanError::MissingCrossSigningIdentity(own_account.user_id().to_owned()) + })?; + let other_identity = store + .get_user_identity(&other_user_id) + .await? + .ok_or_else(|| ScanError::MissingCrossSigningIdentity(other_user_id.clone()))?; + let other_device = + store.get_device(&other_user_id, &other_device_id).await?.ok_or_else(|| { + ScanError::MissingDeviceKeys(other_user_id.clone(), other_device_id.clone()) + })?; + + let check_master_key = |key, identity: &ReadOnlyUserIdentities| { + let master_key = identity.master_key().get_first_key().ok_or_else(|| { + ScanError::MissingCrossSigningIdentity(identity.user_id().clone()) + })?; + + if key != master_key { + Err(ScanError::KeyMismatch { + expected: master_key.to_owned(), + found: qr_code.first_key().to_owned(), + }) + } else { + Ok(()) + } + }; + + let identities = match qr_code { + QrVerificationData::Verification(_) => { + check_master_key(qr_code.first_key(), &other_identity)?; + check_master_key(qr_code.second_key(), &own_identity)?; + + IdentitiesBeingVerified { + private_identity, + store: store.clone(), + device_being_verified: other_device, + identity_being_verified: Some(other_identity), + } + } + QrVerificationData::SelfVerification(_) => { + check_master_key(qr_code.first_key(), &other_identity)?; + if qr_code.second_key() != own_account.identity_keys().ed25519() { + return Err(ScanError::KeyMismatch { + expected: own_account.identity_keys().ed25519().to_owned(), + found: qr_code.second_key().to_owned(), + }); + } + + IdentitiesBeingVerified { + private_identity, + store: store.clone(), + device_being_verified: other_device, + identity_being_verified: Some(other_identity), + } + } + QrVerificationData::SelfVerificationNoMasterKey(_) => { + let device_key = + other_device.get_key(DeviceKeyAlgorithm::Ed25519).ok_or_else(|| { + ScanError::MissingDeviceKeys(other_user_id.clone(), other_device_id.clone()) + })?; + if qr_code.first_key() != device_key { + return Err(ScanError::KeyMismatch { + expected: device_key.to_owned(), + found: qr_code.first_key().to_owned(), + }); + } + check_master_key(qr_code.second_key(), &other_identity)?; + IdentitiesBeingVerified { + private_identity, + store: store.clone(), + device_being_verified: other_device, + identity_being_verified: None, + } + } + }; + + let secret = qr_code.secret().to_owned(); + + Ok(Self { + store, + flow_id, + inner: qr_code.into(), + state: Mutex::new(InnerState::Reciprocated(QrState { + state: Reciprocated { secret, own_device_id: own_account.device_id().to_owned() }, + })) + .into(), + identities, + we_started, + request_handle, + }) + } + + fn new_helper( + store: Arc, + flow_id: FlowId, + inner: QrVerificationData, + identities: IdentitiesBeingVerified, + we_started: bool, + request_handle: Option, + ) -> Self { + let secret = inner.secret().to_owned(); + + Self { + store, + flow_id, + inner: inner.into(), + state: Mutex::new(InnerState::Created(QrState { state: Created { secret } })).into(), + identities, + we_started, + request_handle, + } + } +} + +#[derive(Debug, Clone)] +enum InnerState { + Created(QrState), + Scanned(QrState), + Confirmed(QrState), + Reciprocated(QrState), + Done(QrState), + Cancelled(QrState), +} + +#[derive(Clone, Debug)] +struct QrState { + state: S, +} + +impl QrState { + pub fn into_cancelled(self, content: &CancelContent<'_>) -> QrState { + QrState { state: Cancelled::new(false, content.cancel_code().to_owned()) } + } +} + +#[derive(Clone, Debug)] +struct Created { + secret: String, +} + +#[derive(Clone, Debug)] +struct Scanned {} + +#[derive(Clone, Debug)] +struct Confirmed {} + +#[derive(Clone, Debug)] +struct Reciprocated { + own_device_id: DeviceIdBox, + secret: String, +} + +impl Reciprocated { + fn as_content(&self, flow_id: &FlowId) -> OutgoingContent { + let content = ReciprocateV1Content::new(self.secret.clone()); + let method = StartMethod::ReciprocateV1(content); + + let content: OwnedStartContent = match flow_id { + FlowId::ToDevice(t) => { + StartToDeviceEventContent::new(self.own_device_id.clone(), t.clone(), method).into() + } + FlowId::InRoom(r, e) => ( + r.clone(), + StartEventContent::new( + self.own_device_id.clone(), + method, + Relation::new(e.clone()), + ), + ) + .into(), + }; + + content.into() + } +} + +impl QrState { + fn confirm_scanning(self) -> QrState { + QrState { state: Confirmed {} } + } +} + +impl QrState { + fn new(cancelled_by_us: bool, cancel_code: CancelCode) -> Self { + QrState { state: Cancelled::new(cancelled_by_us, cancel_code) } + } + + fn as_content(&self, flow_id: &FlowId) -> OutgoingContent { + self.state.as_content(flow_id) + } +} + +impl QrState { + fn receive_reciprocate( + self, + content: &StartContent, + ) -> Result, QrState> { + match content.method() { + start::StartMethod::ReciprocateV1(m) => { + // TODO use constant time eq here. + if self.state.secret == m.secret { + Ok(QrState { state: Scanned {} }) + } else { + Err(QrState::::new(false, CancelCode::KeyMismatch)) + } + } + _ => Err(QrState::::new(false, CancelCode::UnknownMethod)), + } + } +} + +impl QrState { + fn as_content(&self, flow_id: &FlowId) -> OutgoingContent { + self.state.as_content(flow_id) + } + + fn verified_identities(&self) -> (Arc<[ReadOnlyDevice]>, Arc<[ReadOnlyUserIdentities]>) { + (self.state.verified_devices.clone(), self.state.verified_master_keys.clone()) + } +} + +impl QrState { + fn into_done( + self, + _: &DoneContent, + verified_device: Option<&ReadOnlyDevice>, + verified_identity: Option<&ReadOnlyUserIdentities>, + ) -> QrState { + let devices: Vec<_> = verified_device.into_iter().cloned().collect(); + let identities: Vec<_> = verified_identity.into_iter().cloned().collect(); + + QrState { + state: Done { + verified_devices: devices.into(), + verified_master_keys: identities.into(), + }, + } + } + + fn as_content(&self, flow_id: &FlowId) -> OutgoingContent { + match flow_id { + FlowId::ToDevice(t) => AnyToDeviceEventContent::KeyVerificationDone( + DoneToDeviceEventContent::new(t.to_owned()), + ) + .into(), + FlowId::InRoom(r, e) => ( + r.to_owned(), + AnyMessageEventContent::KeyVerificationDone(DoneEventContent::new(Relation::new( + e.to_owned(), + ))), + ) + .into(), + } + } +} + +impl QrState { + fn as_content(&self, flow_id: &FlowId) -> OutgoingContent { + self.state.as_content(flow_id) + } + + fn into_done( + self, + _: &DoneContent, + verified_device: Option<&ReadOnlyDevice>, + verified_identity: Option<&ReadOnlyUserIdentities>, + ) -> QrState { + let devices: Vec<_> = verified_device.into_iter().cloned().collect(); + let identities: Vec<_> = verified_identity.into_iter().cloned().collect(); + + QrState { + state: Done { + verified_devices: devices.into(), + verified_master_keys: identities.into(), + }, + } + } +} + +#[cfg(test)] +mod test { + use std::{convert::TryFrom, sync::Arc}; + + use matrix_qrcode::QrVerificationData; + use matrix_sdk_test::async_test; + use ruma::{event_id, room_id, user_id, DeviceIdBox, UserId}; + + use crate::{ + olm::{PrivateCrossSigningIdentity, ReadOnlyAccount}, + store::{Changes, CryptoStore, MemoryStore}, + verification::{ + event_enums::{DoneContent, OutgoingContent, StartContent}, + FlowId, IdentitiesBeingVerified, + }, + QrVerification, ReadOnlyDevice, + }; + + fn user_id() -> UserId { + user_id!("@example:localhost") + } + + fn memory_store() -> Arc { + Arc::new(MemoryStore::new()) + } + + fn device_id() -> DeviceIdBox { + "DEVICEID".into() + } + + #[async_test] + async fn test_verification_creation() { + let store = memory_store(); + + let account = ReadOnlyAccount::new(&user_id(), &device_id()); + let private_identity = PrivateCrossSigningIdentity::new(user_id()).await; + let flow_id = FlowId::ToDevice("test_transaction".to_owned()); + + let device_key = account.identity_keys().ed25519().to_owned(); + let master_key = private_identity.master_public_key().await.unwrap(); + let master_key = master_key.get_first_key().unwrap().to_owned(); + + let alice_device = ReadOnlyDevice::from_account(&account).await; + + let identities = IdentitiesBeingVerified { + private_identity, + store: store.clone(), + device_being_verified: alice_device, + identity_being_verified: None, + }; + + let verification = QrVerification::new_self_no_master( + account, + store.clone(), + flow_id.clone(), + master_key.clone(), + identities.clone(), + false, + None, + ); + + assert_eq!(verification.inner.first_key(), &device_key); + assert_eq!(verification.inner.second_key(), &master_key); + + let verification = QrVerification::new_self( + store.clone(), + flow_id, + master_key.clone(), + device_key.clone(), + identities.clone(), + false, + None, + ); + + assert_eq!(verification.inner.first_key(), &master_key); + assert_eq!(verification.inner.second_key(), &device_key); + + let bob_identity = PrivateCrossSigningIdentity::new(user_id!("@bob:example")).await; + let bob_master_key = bob_identity.master_public_key().await.unwrap(); + let bob_master_key = bob_master_key.get_first_key().unwrap().to_owned(); + + let flow_id = FlowId::InRoom(room_id!("!test:example"), event_id!("$EVENTID")); + + let verification = QrVerification::new_cross( + store.clone(), + flow_id, + master_key.clone(), + bob_master_key.clone(), + identities, + false, + None, + ); + + assert_eq!(verification.inner.first_key(), &master_key); + assert_eq!(verification.inner.second_key(), &bob_master_key); + } + + #[async_test] + async fn test_reciprocate_receival() { + let test = |flow_id: FlowId| async move { + let alice_account = ReadOnlyAccount::new(&user_id(), &device_id()); + let store = memory_store(); + + let bob_account = ReadOnlyAccount::new(alice_account.user_id(), "BOBDEVICE".into()); + + let private_identity = PrivateCrossSigningIdentity::new(user_id()).await; + let identity = private_identity.to_public_identity().await.unwrap(); + + let master_key = private_identity.master_public_key().await.unwrap(); + let master_key = master_key.get_first_key().unwrap().to_owned(); + + let alice_device = ReadOnlyDevice::from_account(&alice_account).await; + let bob_device = ReadOnlyDevice::from_account(&bob_account).await; + + let mut changes = Changes::default(); + changes.identities.new.push(identity.clone().into()); + changes.devices.new.push(bob_device.clone()); + store.save_changes(changes).await.unwrap(); + + let identities = IdentitiesBeingVerified { + private_identity: PrivateCrossSigningIdentity::empty( + alice_account.user_id().to_owned(), + ), + store: store.clone(), + device_being_verified: alice_device.clone(), + identity_being_verified: Some(identity.clone().into()), + }; + + let alice_verification = QrVerification::new_self_no_master( + alice_account.clone(), + store, + flow_id.clone(), + master_key.clone(), + identities, + false, + None, + ); + + let bob_store = memory_store(); + + let mut changes = Changes::default(); + changes.identities.new.push(identity.into()); + changes.devices.new.push(alice_device.clone()); + bob_store.save_changes(changes).await.unwrap(); + + let qr_code = alice_verification.to_bytes().unwrap(); + let qr_code = QrVerificationData::from_bytes(qr_code).unwrap(); + + let bob_verification = QrVerification::from_scan( + bob_store, + bob_account, + private_identity, + alice_account.user_id().to_owned(), + alice_account.device_id().to_owned(), + flow_id, + qr_code, + false, + None, + ) + .await + .unwrap(); + + let request = bob_verification.reciprocate().unwrap(); + let content = OutgoingContent::try_from(request).unwrap(); + let content = StartContent::try_from(&content).unwrap(); + + alice_verification.receive_reciprocation(&content); + + let request = alice_verification.confirm_scanning().unwrap(); + let content = OutgoingContent::try_from(request).unwrap(); + let content = DoneContent::try_from(&content).unwrap(); + + assert!(!alice_verification.is_done()); + assert!(!bob_verification.is_done()); + + let (request, _) = bob_verification.receive_done(&content).await.unwrap(); + let content = OutgoingContent::try_from(request.unwrap()).unwrap(); + let content = DoneContent::try_from(&content).unwrap(); + alice_verification.receive_done(&content).await.unwrap(); + + assert!(alice_verification.is_done()); + assert!(bob_verification.is_done()); + + let identity = alice_verification + .store + .get_user_identity(alice_account.user_id()) + .await + .unwrap() + .unwrap(); + + let identity = identity.own().unwrap(); + + assert!(!bob_device.is_locally_trusted()); + assert!(alice_device.is_locally_trusted()); + assert!(identity.is_verified()); + }; + + let flow_id = FlowId::ToDevice("test_transaction".to_owned()); + test(flow_id).await; + + let flow_id = FlowId::InRoom(room_id!("!test:example"), event_id!("$EVENTID")); + test(flow_id).await; + } +} diff --git a/matrix_sdk_crypto/src/verification/requests.rs b/matrix_sdk_crypto/src/verification/requests.rs index 819d2dc4..f20d6d15 100644 --- a/matrix_sdk_crypto/src/verification/requests.rs +++ b/matrix_sdk_crypto/src/verification/requests.rs @@ -12,13 +12,14 @@ // See the License for the specific language governing permissions and // limitations under the License. -#![allow(dead_code)] +use std::{ + sync::{Arc, Mutex}, + time::Duration, +}; -use std::sync::{Arc, Mutex}; - -use matrix_sdk_common::uuid::Uuid; +use matrix_qrcode::QrVerificationData; +use matrix_sdk_common::{instant::Instant, uuid::Uuid}; use ruma::{ - api::client::r0::to_device::DeviceIdOrAllDevices, events::{ key::verification::{ cancel::CancelCode, @@ -30,50 +31,90 @@ use ruma::{ room::message::KeyVerificationRequestEventContent, AnyMessageEventContent, AnyToDeviceEventContent, }, - DeviceId, DeviceIdBox, EventId, MilliSecondsSinceUnixEpoch, RoomId, UserId, + to_device::DeviceIdOrAllDevices, + DeviceId, DeviceIdBox, DeviceKeyAlgorithm, MilliSecondsSinceUnixEpoch, RoomId, UserId, }; -use tracing::{info, warn}; +use tracing::{info, trace, warn}; use super::{ cache::VerificationCache, event_enums::{ CancelContent, DoneContent, OutgoingContent, ReadyContent, RequestContent, StartContent, }, - sas::content_to_request, - Cancelled, FlowId, + qrcode::{QrVerification, ScanError}, + CancelInfo, Cancelled, FlowId, IdentitiesBeingVerified, }; use crate::{ olm::{PrivateCrossSigningIdentity, ReadOnlyAccount}, store::CryptoStore, - CryptoStoreError, OutgoingVerificationRequest, ReadOnlyDevice, RoomMessageRequest, Sas, - ToDeviceRequest, UserIdentities, + CryptoStoreError, OutgoingVerificationRequest, ReadOnlyDevice, ReadOnlyUserIdentities, + RoomMessageRequest, Sas, ToDeviceRequest, }; -const SUPPORTED_METHODS: &[VerificationMethod] = &[VerificationMethod::MSasV1]; +const SUPPORTED_METHODS: &[VerificationMethod] = &[ + VerificationMethod::SasV1, + VerificationMethod::QrCodeShowV1, + VerificationMethod::ReciprocateV1, +]; +const VERIFICATION_TIMEOUT: Duration = Duration::from_secs(60 * 10); + +/// An object controlling key verification requests. +/// +/// Interactive verification flows usually start with a verification request, +/// this object lets you send and reply to such a verification request. +/// +/// After the initial handshake the verification flow transitions into one of +/// the verification methods. #[derive(Clone, Debug)] -/// TODO pub struct VerificationRequest { verification_cache: VerificationCache, account: ReadOnlyAccount, flow_id: Arc, other_user_id: Arc, inner: Arc>, + creation_time: Arc, + we_started: bool, + recipient_devices: Arc>, +} + +/// A handle to a request so child verification flows can cancel the request. +/// +/// A verification flow can branch off into different types of verification +/// flows after the initial request handshake is done. +/// +/// Cancelling a QR code verification should also cancel the request. This +/// `RequestHandle` allows the QR code verification object to cancel the parent +/// `VerificationRequest` object. +#[derive(Clone, Debug)] +pub(crate) struct RequestHandle { + inner: Arc>, +} + +impl RequestHandle { + pub fn cancel_with_code(&self, cancel_code: &CancelCode) { + self.inner.lock().unwrap().cancel(true, cancel_code) + } +} + +impl From>> for RequestHandle { + fn from(inner: Arc>) -> Self { + Self { inner } + } } impl VerificationRequest { - /// TODO + #[allow(clippy::too_many_arguments)] pub(crate) fn new( cache: VerificationCache, account: ReadOnlyAccount, private_cross_signing_identity: PrivateCrossSigningIdentity, store: Arc, - room_id: &RoomId, - event_id: &EventId, + flow_id: FlowId, other_user: &UserId, + recipient_devices: Vec, + methods: Option>, ) -> Self { - let flow_id = (room_id.to_owned(), event_id.to_owned()).into(); - let inner = Mutex::new(InnerRequest::Created(RequestState::new( account.clone(), private_cross_signing_identity, @@ -81,6 +122,7 @@ impl VerificationRequest { store, other_user, &flow_id, + methods, ))) .into(); @@ -90,53 +132,49 @@ impl VerificationRequest { flow_id: flow_id.into(), inner, other_user_id: other_user.to_owned().into(), + creation_time: Instant::now().into(), + we_started: true, + recipient_devices: recipient_devices.into(), } } - /// TODO - pub(crate) fn new_to_device( - cache: VerificationCache, - account: ReadOnlyAccount, - private_cross_signing_identity: PrivateCrossSigningIdentity, - store: Arc, - other_user: &UserId, - ) -> Self { - let flow_id = Uuid::new_v4().to_string().into(); + /// Create an event content that can be sent as a to-device event to request + /// verification from the other side. This should be used only for + /// self-verifications and it should be sent to the specific device that we + /// want to verify. + pub(crate) fn request_to_device(&self) -> ToDeviceRequest { + let inner = self.inner.lock().unwrap(); - let inner = Mutex::new(InnerRequest::Created(RequestState::new( - account.clone(), - private_cross_signing_identity, - cache.clone(), - store, - other_user, - &flow_id, - ))) - .into(); + let methods = if let InnerRequest::Created(c) = &*inner { + c.state.our_methods.clone() + } else { + SUPPORTED_METHODS.to_vec() + }; - Self { - account, - verification_cache: cache, - flow_id: flow_id.into(), - inner, - other_user_id: other_user.to_owned().into(), - } - } - - /// TODO - pub fn request_to_device(&self) -> RequestToDeviceEventContent { - RequestToDeviceEventContent::new( + let content = RequestToDeviceEventContent::new( self.account.device_id().into(), self.flow_id().as_str().to_string(), - SUPPORTED_METHODS.to_vec(), + methods, MilliSecondsSinceUnixEpoch::now(), + ); + + ToDeviceRequest::new_for_recipients( + self.other_user(), + self.recipient_devices.to_vec(), + AnyToDeviceEventContent::KeyVerificationRequest(content), + Uuid::new_v4(), ) } - /// TODO + /// Create an event content that can be sent as a room event to request + /// verification from the other side. This should be used only for + /// verifications of other users and it should be sent to a room we consider + /// to be a DM with the other user. pub fn request( own_user_id: &UserId, own_device_id: &DeviceId, other_user_id: &UserId, + methods: Option>, ) -> KeyVerificationRequestEventContent { KeyVerificationRequestEventContent::new( format!( @@ -145,23 +183,113 @@ impl VerificationRequest { key verification to verify keys.", own_user_id ), - SUPPORTED_METHODS.to_vec(), + methods.unwrap_or_else(|| SUPPORTED_METHODS.to_vec()), own_device_id.into(), other_user_id.to_owned(), ) } + /// Our own user id. + pub fn own_user_id(&self) -> &UserId { + self.account.user_id() + } + /// The id of the other user that is participating in this verification /// request. pub fn other_user(&self) -> &UserId { &self.other_user_id } + /// The id of the other device that is participating in this verification. + pub fn other_device_id(&self) -> Option { + match &*self.inner.lock().unwrap() { + InnerRequest::Requested(r) => Some(r.state.other_device_id.clone()), + InnerRequest::Ready(r) => Some(r.state.other_device_id.clone()), + InnerRequest::Created(_) + | InnerRequest::Passive(_) + | InnerRequest::Done(_) + | InnerRequest::Cancelled(_) => None, + } + } + + /// Get the room id if the verification is happening inside a room. + pub fn room_id(&self) -> Option<&RoomId> { + match self.flow_id.as_ref() { + FlowId::ToDevice(_) => None, + FlowId::InRoom(r, _) => Some(r), + } + } + + /// Get info about the cancellation if the verification request has been + /// cancelled. + pub fn cancel_info(&self) -> Option { + if let InnerRequest::Cancelled(c) = &*self.inner.lock().unwrap() { + Some(c.state.clone().into()) + } else { + None + } + } + + /// Has the verification request been answered by another device. + pub fn is_passive(&self) -> bool { + matches!(&*self.inner.lock().unwrap(), InnerRequest::Passive(_)) + } + + /// Is the verification request ready to start a verification flow. + pub fn is_ready(&self) -> bool { + matches!(&*self.inner.lock().unwrap(), InnerRequest::Ready(_)) + } + + /// Has the verification flow timed out. + pub fn timed_out(&self) -> bool { + self.creation_time.elapsed() > VERIFICATION_TIMEOUT + } + + /// Get the supported verification methods of the other side. + /// + /// Will be present only if the other side requested the verification or if + /// we're in the ready state. + pub fn their_supported_methods(&self) -> Option> { + match &*self.inner.lock().unwrap() { + InnerRequest::Requested(r) => Some(r.state.their_methods.clone()), + InnerRequest::Ready(r) => Some(r.state.their_methods.clone()), + InnerRequest::Created(_) + | InnerRequest::Passive(_) + | InnerRequest::Done(_) + | InnerRequest::Cancelled(_) => None, + } + } + + /// Get our own supported verification methods that we advertised. + /// + /// Will be present only we requested the verification or if we're in the + /// ready state. + pub fn our_supported_methods(&self) -> Option> { + match &*self.inner.lock().unwrap() { + InnerRequest::Created(r) => Some(r.state.our_methods.clone()), + InnerRequest::Ready(r) => Some(r.state.our_methods.clone()), + InnerRequest::Requested(_) + | InnerRequest::Passive(_) + | InnerRequest::Done(_) + | InnerRequest::Cancelled(_) => None, + } + } + /// Get the unique ID of this verification request pub fn flow_id(&self) -> &FlowId { &self.flow_id } + /// Is this a verification that is veryfying one of our own devices + pub fn is_self_verification(&self) -> bool { + self.account.user_id() == self.other_user() + } + + /// Did we initiate the verification request + pub fn we_started(&self) -> bool { + self.we_started + } + /// Has the verification flow that was started with this request finished. pub fn is_done(&self) -> bool { matches!(&*self.inner.lock().unwrap(), InnerRequest::Done(_)) @@ -173,6 +301,51 @@ impl VerificationRequest { matches!(&*self.inner.lock().unwrap(), InnerRequest::Cancelled(_)) } + /// Generate a QR code that can be used by another client to start a QR code + /// based verification. + pub async fn generate_qr_code(&self) -> Result, CryptoStoreError> { + self.inner + .lock() + .unwrap() + .generate_qr_code(self.we_started, self.inner.clone().into()) + .await + } + + /// Start a QR code verification by providing a scanned QR code for this + /// verification flow. + /// + /// Returns a `ScanError` if the QR code isn't valid, `None` if the + /// verification request isn't in the ready state or we don't support QR + /// code verification, otherwise a newly created `QrVerification` object + /// which will be used for the remainder of the verification flow. + pub async fn scan_qr_code( + &self, + data: QrVerificationData, + ) -> Result, ScanError> { + let state = self.inner.lock().unwrap(); + + if let InnerRequest::Ready(r) = &*state { + let qr_verification = QrVerification::from_scan( + r.store.clone(), + r.account.clone(), + r.private_cross_signing_identity.clone(), + r.other_user_id.clone(), + r.state.other_device_id.clone(), + r.flow_id.as_ref().to_owned(), + data, + self.we_started, + Some(self.inner.clone().into()), + ) + .await?; + + self.verification_cache.insert_qr(qr_verification.clone()); + + Ok(Some(qr_verification)) + } else { + Ok(None) + } + } + pub(crate) fn from_request( cache: VerificationCache, account: ReadOnlyAccount, @@ -196,16 +369,27 @@ impl VerificationRequest { account, other_user_id: sender.to_owned().into(), flow_id: flow_id.into(), + we_started: false, + creation_time: Instant::now().into(), + recipient_devices: vec![].into(), } } - /// Accept the verification request. - pub fn accept(&self) -> Option { + /// Accept the verification request signaling that our client supports the + /// given verification methods. + /// + /// # Arguments + /// + /// * `methods` - The methods that we should advertise as supported by us. + pub fn accept_with_methods( + &self, + methods: Vec, + ) -> Option { let mut inner = self.inner.lock().unwrap(); - inner.accept().map(|c| match c { + inner.accept(methods).map(|c| match c { OutgoingContent::ToDevice(content) => { - self.content_to_request(inner.other_device_id(), content).into() + ToDeviceRequest::new(&self.other_user(), inner.other_device_id(), content).into() } OutgoingContent::Room(room_id, content) => { RoomMessageRequest { room_id, txn_id: Uuid::new_v4(), content }.into() @@ -213,15 +397,158 @@ impl VerificationRequest { }) } - #[allow(clippy::unnecessary_wraps)] - pub(crate) fn receive_ready(&self, sender: &UserId, content: &ReadyContent) -> Result<(), ()> { + /// Accept the verification request. + /// + /// This method will accept the request and signal that it supports the + /// `m.sas.v1`, the `m.qr_code.show.v1`, and `m.reciprocate.v1` method. + /// + /// If QR code scanning should be supported or QR code showing shouldn't be + /// supported the [`accept_with_methods()`] method should be used instead. + /// + /// [`accept_with_methods()`]: #method.accept_with_methods + pub fn accept(&self) -> Option { + self.accept_with_methods(SUPPORTED_METHODS.to_vec()) + } + + /// Cancel the verification request + pub fn cancel(&self) -> Option { + self.cancel_with_code(CancelCode::User) + } + + fn cancel_with_code(&self, cancel_code: CancelCode) -> Option { let mut inner = self.inner.lock().unwrap(); - if let InnerRequest::Created(s) = &*inner { - *inner = InnerRequest::Ready(s.clone().into_ready(sender, content)); + let send_to_everyone = self.we_started() && matches!(&*inner, InnerRequest::Created(_)); + let other_device = inner.other_device_id(); + + inner.cancel(true, &cancel_code); + + let content = if let InnerRequest::Cancelled(c) = &*inner { + Some(c.state.as_content(self.flow_id())) + } else { + None + }; + + let request = content.map(|c| match c { + OutgoingContent::ToDevice(content) => { + if send_to_everyone { + ToDeviceRequest::new_for_recipients( + &self.other_user(), + self.recipient_devices.to_vec(), + content, + Uuid::new_v4(), + ) + .into() + } else { + ToDeviceRequest::new(&self.other_user(), other_device, content).into() + } + } + OutgoingContent::Room(room_id, content) => { + RoomMessageRequest { room_id, txn_id: Uuid::new_v4(), content }.into() + } + }); + + drop(inner); + + if let Some(verification) = + self.verification_cache.get(self.other_user(), self.flow_id().as_str()) + { + match verification { + crate::Verification::SasV1(s) => s.cancel_with_code(cancel_code), + crate::Verification::QrV1(q) => q.cancel_with_code(cancel_code), + }; } - Ok(()) + request + } + + pub(crate) fn cancel_if_timed_out(&self) -> Option { + if self.is_cancelled() || self.is_done() { + None + } else if self.timed_out() { + let request = self.cancel_with_code(CancelCode::Timeout); + + if self.is_passive() { + None + } else { + request + } + } else { + None + } + } + + /// Create a key verification cancellation for devices that received the + /// request but either shouldn't continue in the verification or didn't get + /// notified that the other side cancelled. + /// + /// The spec states the following[1]: + /// When Bob accepts or declines the verification on one of his devices + /// (sending either an m.key.verification.ready or m.key.verification.cancel + /// event), Alice will send an m.key.verification.cancel event to Bob’s + /// other devices with a code of m.accepted in the case where Bob accepted + /// the verification, or m.user in the case where Bob rejected the + /// verification. + /// + /// Realistically sending the cancellation to Bob's other devices is only + /// possible if Bob accepted the verification since we don't know the device + /// id of Bob's device that rejected the verification. + /// + /// Thus, we're sending the cancellation to all devices that received the + /// request in the rejection case. + /// + /// [1]: https://spec.matrix.org/unstable/client-server-api/#key-verification-framework + pub(crate) fn cancel_for_other_devices( + &self, + code: CancelCode, + filter_device: Option<&DeviceId>, + ) -> Option { + let cancelled = Cancelled::new(true, code); + let cancel_content = cancelled.as_content(self.flow_id()); + + if let OutgoingContent::ToDevice(c) = cancel_content { + let recipients: Vec = self + .recipient_devices + .to_vec() + .into_iter() + .filter(|d| if let Some(device) = filter_device { &**d != device } else { true }) + .collect(); + + Some(ToDeviceRequest::new_for_recipients( + self.other_user(), + recipients, + c, + Uuid::new_v4(), + )) + } else { + None + } + } + + pub(crate) fn receive_ready(&self, sender: &UserId, content: &ReadyContent) { + let mut inner = self.inner.lock().unwrap(); + + match &*inner { + InnerRequest::Created(s) => { + *inner = InnerRequest::Ready(s.clone().into_ready(sender, content)); + + if let Some(request) = + self.cancel_for_other_devices(CancelCode::Accepted, Some(content.from_device())) + { + self.verification_cache.add_verification_request(request.into()); + } + } + InnerRequest::Requested(s) => { + if sender == self.own_user_id() && content.from_device() != self.account.device_id() + { + *inner = InnerRequest::Passive(s.clone().into_passive(content)) + } + } + InnerRequest::Ready(_) + | InnerRequest::Passive(_) + | InnerRequest::Done(_) + | InnerRequest::Cancelled(_) => {} + } } pub(crate) async fn receive_start( @@ -232,7 +559,7 @@ impl VerificationRequest { let inner = self.inner.lock().unwrap().clone(); if let InnerRequest::Ready(s) = inner { - s.receive_start(sender, content).await?; + s.receive_start(sender, content, self.we_started, self.inner.clone().into()).await?; } else { warn!( sender = sender.as_str(), @@ -253,39 +580,64 @@ impl VerificationRequest { pub(crate) fn receive_cancel(&self, sender: &UserId, content: &CancelContent<'_>) { if sender == self.other_user() { - let mut inner = self.inner.lock().unwrap().clone(); - inner.cancel(content.cancel_code()); + trace!( + sender = sender.as_str(), + code = content.cancel_code().as_str(), + "Cancelling a verification request, other user has cancelled" + ); + let mut inner = self.inner.lock().unwrap(); + inner.cancel(false, content.cancel_code()); + + if self.we_started() { + if let Some(request) = + self.cancel_for_other_devices(content.cancel_code().to_owned(), None) + { + self.verification_cache.add_verification_request(request.into()); + } + } } } - /// Is the verification request ready to start a verification flow. - pub fn is_ready(&self) -> bool { - matches!(&*self.inner.lock().unwrap(), InnerRequest::Ready(_)) - } - - pub(crate) fn start( + /// Transition from this verification request into a SAS verification flow. + pub async fn start_sas( &self, - device: ReadOnlyDevice, - user_identity: Option, - ) -> Option<(Sas, OutgoingContent)> { - match &*self.inner.lock().unwrap() { - InnerRequest::Ready(s) => Some(s.clone().start_sas( - s.store.clone(), - s.account.clone(), - s.private_cross_signing_identity.clone(), - device, - user_identity, - )), + ) -> Result, CryptoStoreError> { + let inner = self.inner.lock().unwrap().clone(); + + Ok(match &inner { + InnerRequest::Ready(s) => { + if let Some((sas, content)) = s + .clone() + .start_sas( + s.store.clone(), + s.account.clone(), + s.private_cross_signing_identity.clone(), + self.we_started, + self.inner.clone().into(), + ) + .await? + { + self.verification_cache.insert_sas(sas.clone()); + + let request = match content { + OutgoingContent::ToDevice(content) => ToDeviceRequest::new( + &self.other_user(), + inner.other_device_id(), + content, + ) + .into(), + OutgoingContent::Room(room_id, content) => { + RoomMessageRequest { room_id, txn_id: Uuid::new_v4(), content }.into() + } + }; + + Some((sas, request)) + } else { + None + } + } _ => None, - } - } - - fn content_to_request( - &self, - other_device_id: DeviceIdOrAllDevices, - content: AnyToDeviceEventContent, - ) -> ToDeviceRequest { - content_to_request(&self.other_user_id, other_device_id, content) + }) } } @@ -313,20 +665,9 @@ impl InnerRequest { } } - fn other_user_id(&self) -> &UserId { - match self { - InnerRequest::Created(s) => &s.other_user_id, - InnerRequest::Requested(s) => &s.other_user_id, - InnerRequest::Ready(s) => &s.other_user_id, - InnerRequest::Passive(s) => &s.other_user_id, - InnerRequest::Done(s) => &s.other_user_id, - InnerRequest::Cancelled(s) => &s.other_user_id, - } - } - - fn accept(&mut self) -> Option { + fn accept(&mut self, methods: Vec) -> Option { if let InnerRequest::Requested(s) = self { - let (state, content) = s.clone().accept(); + let (state, content) = s.clone().accept(methods); *self = InnerRequest::Ready(state); Some(content) @@ -337,36 +678,42 @@ impl InnerRequest { fn receive_done(&mut self, content: &DoneContent) { *self = InnerRequest::Done(match self { - InnerRequest::Created(s) => s.clone().into_done(content), - InnerRequest::Requested(s) => s.clone().into_done(content), InnerRequest::Ready(s) => s.clone().into_done(content), InnerRequest::Passive(s) => s.clone().into_done(content), - InnerRequest::Done(s) => s.clone().into_done(content), - InnerRequest::Cancelled(_) => return, + InnerRequest::Done(_) + | InnerRequest::Created(_) + | InnerRequest::Requested(_) + | InnerRequest::Cancelled(_) => return, }) } - fn cancel(&mut self, cancel_code: &CancelCode) { + fn cancel(&mut self, cancelled_by_us: bool, cancel_code: &CancelCode) { + trace!( + cancelled_by_us = cancelled_by_us, + code = cancel_code.as_str(), + "Verification request going into the cancelled state" + ); + *self = InnerRequest::Cancelled(match self { - InnerRequest::Created(s) => s.clone().into_canceled(cancel_code), - InnerRequest::Requested(s) => s.clone().into_canceled(cancel_code), - InnerRequest::Ready(s) => s.clone().into_canceled(cancel_code), - InnerRequest::Passive(s) => s.clone().into_canceled(cancel_code), - InnerRequest::Done(_) => return, - InnerRequest::Cancelled(_) => return, - }) + InnerRequest::Created(s) => s.clone().into_canceled(cancelled_by_us, cancel_code), + InnerRequest::Requested(s) => s.clone().into_canceled(cancelled_by_us, cancel_code), + InnerRequest::Ready(s) => s.clone().into_canceled(cancelled_by_us, cancel_code), + InnerRequest::Passive(_) | InnerRequest::Done(_) | InnerRequest::Cancelled(_) => return, + }); } - fn to_started_sas( + async fn generate_qr_code( &self, - content: &StartContent, - other_device: ReadOnlyDevice, - other_identity: Option, - ) -> Result, OutgoingContent> { - if let InnerRequest::Ready(s) = self { - Ok(Some(s.to_started_sas(content, other_device, other_identity)?)) - } else { - Ok(None) + we_started: bool, + request_handle: RequestHandle, + ) -> Result, CryptoStoreError> { + match self { + InnerRequest::Created(_) => Ok(None), + InnerRequest::Requested(_) => Ok(None), + InnerRequest::Ready(s) => s.generate_qr_code(we_started, request_handle).await, + InnerRequest::Passive(_) => Ok(None), + InnerRequest::Done(_) => Ok(None), + InnerRequest::Cancelled(_) => Ok(None), } } } @@ -399,7 +746,11 @@ impl RequestState { } } - fn into_canceled(self, cancel_code: &CancelCode) -> RequestState { + fn into_canceled( + self, + cancelled_by_us: bool, + cancel_code: &CancelCode, + ) -> RequestState { RequestState:: { account: self.account, private_cross_signing_identity: self.private_cross_signing_identity, @@ -407,7 +758,7 @@ impl RequestState { store: self.store, flow_id: self.flow_id, other_user_id: self.other_user_id, - state: Cancelled::new(cancel_code.clone()), + state: Cancelled::new(cancelled_by_us, cancel_code.clone()), } } } @@ -420,12 +771,15 @@ impl RequestState { store: Arc, other_user_id: &UserId, flow_id: &FlowId, + methods: Option>, ) -> Self { + let our_methods = methods.unwrap_or_else(|| SUPPORTED_METHODS.to_vec()); + Self { account, other_user_id: other_user_id.to_owned(), private_cross_signing_identity: private_identity, - state: Created { methods: SUPPORTED_METHODS.to_vec(), flow_id: flow_id.to_owned() }, + state: Created { our_methods }, verification_cache: cache, store, flow_id: flow_id.to_owned().into(), @@ -442,9 +796,9 @@ impl RequestState { store: self.store, other_user_id: self.other_user_id, state: Ready { - methods: content.methods().to_owned(), + their_methods: content.methods().to_owned(), + our_methods: self.state.our_methods, other_device_id: content.from_device().into(), - flow_id: self.state.flow_id, }, } } @@ -452,22 +806,14 @@ impl RequestState { #[derive(Clone, Debug)] struct Created { - /// The verification methods supported by the sender. - pub methods: Vec, - - /// The event id of our `m.key.verification.request` event which acts as an - /// unique id identifying this verification flow. - pub flow_id: FlowId, + /// The verification methods supported by us. + pub our_methods: Vec, } #[derive(Clone, Debug)] struct Requested { /// The verification methods supported by the sender. - pub methods: Vec, - - /// The event id of the `m.key.verification.request` event which acts as an - /// unique id identifying this verification flow. - pub flow_id: FlowId, + pub their_methods: Vec, /// The device id of the device that responded to the verification request. pub other_device_id: DeviceIdBox, @@ -492,43 +838,54 @@ impl RequestState { flow_id: flow_id.to_owned().into(), other_user_id: sender.clone(), state: Requested { - methods: content.methods().to_owned(), - flow_id: flow_id.clone(), + their_methods: content.methods().to_owned(), other_device_id: content.from_device().into(), }, } } - fn accept(self) -> (RequestState, OutgoingContent) { + fn into_passive(self, content: &ReadyContent) -> RequestState { + RequestState { + account: self.account, + flow_id: self.flow_id, + verification_cache: self.verification_cache, + private_cross_signing_identity: self.private_cross_signing_identity, + store: self.store, + other_user_id: self.other_user_id, + state: Passive { other_device_id: content.from_device().to_owned() }, + } + } + + fn accept(self, methods: Vec) -> (RequestState, OutgoingContent) { let state = RequestState { account: self.account.clone(), store: self.store, verification_cache: self.verification_cache, private_cross_signing_identity: self.private_cross_signing_identity, - flow_id: self.flow_id, + flow_id: self.flow_id.clone(), other_user_id: self.other_user_id, state: Ready { - methods: SUPPORTED_METHODS.to_vec(), + their_methods: self.state.their_methods, + our_methods: methods.clone(), other_device_id: self.state.other_device_id.clone(), - flow_id: self.state.flow_id.clone(), }, }; - let content = match self.state.flow_id { + let content = match self.flow_id.as_ref() { FlowId::ToDevice(i) => { AnyToDeviceEventContent::KeyVerificationReady(ReadyToDeviceEventContent::new( self.account.device_id().to_owned(), - SUPPORTED_METHODS.to_vec(), - i, + methods, + i.to_owned(), )) .into() } FlowId::InRoom(r, e) => ( - r, + r.to_owned(), AnyMessageEventContent::KeyVerificationReady(ReadyEventContent::new( self.account.device_id().to_owned(), - SUPPORTED_METHODS.to_vec(), - Relation::new(e), + methods, + Relation::new(e.to_owned()), )), ) .into(), @@ -540,15 +897,14 @@ impl RequestState { #[derive(Clone, Debug)] struct Ready { - /// The verification methods supported by the sender. - pub methods: Vec, + /// The verification methods supported by the other side. + pub their_methods: Vec, + + /// The verification methods supported by the us. + pub our_methods: Vec, /// The device id of the device that responded to the verification request. pub other_device_id: DeviceIdBox, - - /// The event id of the `m.key.verification.request` event which acts as an - /// unique id identifying this verification flow. - pub flow_id: FlowId, } impl RequestState { @@ -556,7 +912,9 @@ impl RequestState { &self, content: &StartContent<'a>, other_device: ReadOnlyDevice, - other_identity: Option, + other_identity: Option, + we_started: bool, + request_handle: RequestHandle, ) -> Result { Sas::from_start_event( (&*self.flow_id).to_owned(), @@ -566,14 +924,156 @@ impl RequestState { self.private_cross_signing_identity.clone(), other_device, other_identity, - true, + Some(request_handle), + we_started, ) } + async fn generate_qr_code( + &self, + we_started: bool, + request_handle: RequestHandle, + ) -> Result, CryptoStoreError> { + // If we didn't state that we support showing QR codes or if the other + // side doesn't support scanning QR codes bail early. + if !self.state.our_methods.contains(&VerificationMethod::QrCodeShowV1) + || !self.state.their_methods.contains(&VerificationMethod::QrCodeScanV1) + { + return Ok(None); + } + + let device = if let Some(device) = + self.store.get_device(&self.other_user_id, &self.state.other_device_id).await? + { + device + } else { + warn!( + user_id = self.other_user_id.as_str(), + device_id = self.state.other_device_id.as_str(), + "Can't create a QR code, the device that accepted the \ + verification doesn't exist" + ); + return Ok(None); + }; + + let identites = IdentitiesBeingVerified { + private_identity: self.private_cross_signing_identity.clone(), + store: self.store.clone(), + device_being_verified: device, + identity_being_verified: self.store.get_user_identity(&self.other_user_id).await?, + }; + + let verification = if let Some(identity) = &identites.identity_being_verified { + match &identity { + ReadOnlyUserIdentities::Own(i) => { + if let Some(master_key) = i.master_key().get_first_key() { + if identites.can_sign_devices().await { + if let Some(device_key) = + identites.other_device().get_key(DeviceKeyAlgorithm::Ed25519) + { + Some(QrVerification::new_self( + self.store.clone(), + self.flow_id.as_ref().to_owned(), + master_key.to_owned(), + device_key.to_owned(), + identites, + we_started, + Some(request_handle), + )) + } else { + warn!( + user_id = self.other_user_id.as_str(), + device_id = self.state.other_device_id.as_str(), + "Can't create a QR code, the other device \ + doesn't have a valid device key" + ); + None + } + } else { + Some(QrVerification::new_self_no_master( + self.account.clone(), + self.store.clone(), + self.flow_id.as_ref().to_owned(), + master_key.to_owned(), + identites, + we_started, + Some(request_handle), + )) + } + } else { + warn!( + user_id = self.other_user_id.as_str(), + device_id = self.state.other_device_id.as_str(), + "Can't create a QR code, our cross signing identity \ + doesn't contain a valid master key" + ); + None + } + } + ReadOnlyUserIdentities::Other(i) => { + if let Some(other_master) = i.master_key().get_first_key() { + // TODO we can get the master key from the public + // identity if we don't have the private one and we + // trust the public one. + if let Some(own_master) = self + .private_cross_signing_identity + .master_public_key() + .await + .and_then(|m| m.get_first_key().map(|m| m.to_owned())) + { + Some(QrVerification::new_cross( + self.store.clone(), + self.flow_id.as_ref().to_owned(), + own_master, + other_master.to_owned(), + identites, + we_started, + Some(request_handle), + )) + } else { + warn!( + user_id = self.other_user_id.as_str(), + device_id = self.state.other_device_id.as_str(), + "Can't create a QR code, we don't trust our own \ + master key" + ); + None + } + } else { + warn!( + user_id = self.other_user_id.as_str(), + device_id = self.state.other_device_id.as_str(), + "Can't create a QR code, the user's identity \ + doesn't have a valid master key" + ); + None + } + } + } + } else { + warn!( + user_id = self.other_user_id.as_str(), + device_id = self.state.other_device_id.as_str(), + "Can't create a QR code, the user doesn't have a valid cross \ + signing identity." + ); + + None + }; + + if let Some(verification) = &verification { + self.verification_cache.insert_qr(verification.clone()); + } + + Ok(verification) + } + async fn receive_start( &self, sender: &UserId, content: &StartContent<'_>, + we_started: bool, + request_handle: RequestHandle, ) -> Result<(), CryptoStoreError> { info!( sender = sender.as_str(), @@ -596,25 +1096,52 @@ impl RequestState { let identity = self.store.get_user_identity(sender).await?; match content.method() { - StartMethod::SasV1(_) => match self.to_started_sas(content, device.clone(), identity) { - Ok(s) => { - info!("Started a new SAS verification."); - self.verification_cache.insert_sas(s); + StartMethod::SasV1(_) => { + match self.to_started_sas( + content, + device.clone(), + identity, + we_started, + request_handle, + ) { + // TODO check if there is already a SAS verification, i.e. we + // already started one before the other side tried to do the + // same; ignore it if we did and we're the lexicographically + // smaller user ID, otherwise auto-accept the newly started one. + Ok(s) => { + info!("Started a new SAS verification."); + self.verification_cache.insert_sas(s); + } + Err(c) => { + warn!( + user_id = device.user_id().as_str(), + device_id = device.device_id().as_str(), + content =? c, + "Can't start key verification, canceling.", + ); + self.verification_cache.queue_up_content( + device.user_id(), + device.device_id(), + c, + ) + } } - Err(c) => { - warn!( - user_id = device.user_id().as_str(), + } + StartMethod::ReciprocateV1(_) => { + if let Some(qr_verification) = + self.verification_cache.get_qr(sender, content.flow_id()) + { + if let Some(request) = qr_verification.receive_reciprocation(content) { + self.verification_cache.add_request(request.into()) + } + trace!( + sender = device.user_id().as_str(), device_id = device.device_id().as_str(), - content =? c, - "Can't start key verification, canceling.", - ); - self.verification_cache.queue_up_content( - device.user_id(), - device.device_id(), - c, + verification =? qr_verification, + "Received a QR code reciprocation" ) } - }, + } m => { warn!(method =? m, "Received a key verification start event with an unsupported method") } @@ -623,39 +1150,64 @@ impl RequestState { Ok(()) } - fn start_sas( + async fn start_sas( self, store: Arc, account: ReadOnlyAccount, private_identity: PrivateCrossSigningIdentity, - other_device: ReadOnlyDevice, - other_identity: Option, - ) -> (Sas, OutgoingContent) { - match self.state.flow_id { + we_started: bool, + request_handle: RequestHandle, + ) -> Result, CryptoStoreError> { + if !self.state.their_methods.contains(&VerificationMethod::SasV1) { + return Ok(None); + } + + // TODO signal why starting the sas flow doesn't work? + let other_identity = store.get_user_identity(&self.other_user_id).await?; + + let device = if let Some(device) = + self.store.get_device(&self.other_user_id, &self.state.other_device_id).await? + { + device + } else { + warn!( + user_id = self.other_user_id.as_str(), + device_id = self.state.other_device_id.as_str(), + "Can't start the SAS verificaiton flow, the device that \ + accepted the verification doesn't exist" + ); + return Ok(None); + }; + + Ok(Some(match self.flow_id.as_ref() { FlowId::ToDevice(t) => { let (sas, content) = Sas::start( account, private_identity, - other_device, + device, store, other_identity, - Some(t), + Some(t.to_owned()), + we_started, + Some(request_handle), ); (sas, content) } FlowId::InRoom(r, e) => { let (sas, content) = Sas::start_in_room( - e, - r, + e.to_owned(), + r.to_owned(), account, private_identity, - other_device, + device, store, other_identity, + we_started, + request_handle, ); (sas, content) } - } + })) } } @@ -663,10 +1215,6 @@ impl RequestState { struct Passive { /// The device id of the device that responded to the verification request. pub other_device_id: DeviceIdBox, - - /// The event id of the `m.key.verification.request` event which acts as an - /// unique id identifying this verification flow. - pub flow_id: FlowId, } #[derive(Clone, Debug)] @@ -674,7 +1222,7 @@ struct Done {} #[cfg(test)] mod test { - use std::convert::TryFrom; + use std::convert::{TryFrom, TryInto}; use matrix_sdk_test::async_test; use ruma::{event_id, room_id, DeviceIdBox, UserId}; @@ -685,7 +1233,7 @@ mod test { store::{Changes, CryptoStore, MemoryStore}, verification::{ cache::VerificationCache, - event_enums::{OutgoingContent, ReadyContent, StartContent}, + event_enums::{OutgoingContent, ReadyContent, RequestContent, StartContent}, FlowId, }, ReadOnlyDevice, @@ -720,20 +1268,22 @@ mod test { let bob_store: Box = Box::new(MemoryStore::new()); let bob_identity = PrivateCrossSigningIdentity::empty(alice_id()); - let content = VerificationRequest::request(bob.user_id(), bob.device_id(), &alice_id()); + let content = + VerificationRequest::request(bob.user_id(), bob.device_id(), &alice_id(), None); + + let flow_id = FlowId::InRoom(room_id, event_id); let bob_request = VerificationRequest::new( VerificationCache::new(), bob, bob_identity, bob_store.into(), - &room_id, - &event_id, + flow_id.clone(), &alice_id(), + vec![], + None, ); - let flow_id = FlowId::from((room_id, event_id)); - let alice_request = VerificationRequest::from_request( VerificationCache::new(), alice, @@ -744,10 +1294,10 @@ mod test { &(&content).into(), ); - let content: OutgoingContent = alice_request.accept().unwrap().into(); + let content: OutgoingContent = alice_request.accept().unwrap().try_into().unwrap(); let content = ReadyContent::try_from(&content).unwrap(); - bob_request.receive_ready(&alice_id(), &content).unwrap(); + bob_request.receive_ready(&alice_id(), &content); assert!(bob_request.is_ready()); assert!(alice_request.is_ready()); @@ -773,20 +1323,25 @@ mod test { changes.devices.new.push(bob_device.clone()); alice_store.save_changes(changes).await.unwrap(); - let content = VerificationRequest::request(bob.user_id(), bob.device_id(), &alice_id()); + let mut changes = Changes::default(); + changes.devices.new.push(alice_device.clone()); + bob_store.save_changes(changes).await.unwrap(); + + let content = + VerificationRequest::request(bob.user_id(), bob.device_id(), &alice_id(), None); + let flow_id = FlowId::from((room_id, event_id)); let bob_request = VerificationRequest::new( VerificationCache::new(), bob, bob_identity, bob_store.into(), - &room_id, - &event_id, + flow_id.clone(), &alice_id(), + vec![], + None, ); - let flow_id = FlowId::from((room_id, event_id)); - let alice_request = VerificationRequest::from_request( VerificationCache::new(), alice, @@ -797,20 +1352,22 @@ mod test { &(&content).into(), ); - let content: OutgoingContent = alice_request.accept().unwrap().into(); + let content: OutgoingContent = alice_request.accept().unwrap().try_into().unwrap(); let content = ReadyContent::try_from(&content).unwrap(); - bob_request.receive_ready(&alice_id(), &content).unwrap(); + bob_request.receive_ready(&alice_id(), &content); assert!(bob_request.is_ready()); assert!(alice_request.is_ready()); - let (bob_sas, start_content) = bob_request.start(alice_device, None).unwrap(); + let (bob_sas, request) = bob_request.start_sas().await.unwrap().unwrap(); - let content = StartContent::try_from(&start_content).unwrap(); + let content: OutgoingContent = request.try_into().unwrap(); + let content = StartContent::try_from(&content).unwrap(); let flow_id = content.flow_id().to_owned(); alice_request.receive_start(bob_device.user_id(), &content).await.unwrap(); - let alice_sas = alice_request.verification_cache.get_sas(&flow_id).unwrap(); + let alice_sas = + alice_request.verification_cache.get_sas(bob_device.user_id(), &flow_id).unwrap(); assert!(!bob_sas.is_cancelled()); assert!(!alice_sas.is_cancelled()); @@ -833,15 +1390,26 @@ mod test { changes.devices.new.push(bob_device.clone()); alice_store.save_changes(changes).await.unwrap(); - let bob_request = VerificationRequest::new_to_device( + let mut changes = Changes::default(); + changes.devices.new.push(alice_device.clone()); + bob_store.save_changes(changes).await.unwrap(); + + let flow_id = FlowId::from("TEST_FLOW_ID".to_owned()); + + let bob_request = VerificationRequest::new( VerificationCache::new(), bob, bob_identity, bob_store.into(), + flow_id, &alice_id(), + vec![], + None, ); - let content = bob_request.request_to_device(); + let request = bob_request.request_to_device(); + let content: OutgoingContent = request.try_into().unwrap(); + let content = RequestContent::try_from(&content).unwrap(); let flow_id = bob_request.flow_id().to_owned(); let alice_request = VerificationRequest::from_request( @@ -851,23 +1419,25 @@ mod test { alice_store.into(), &bob_id(), flow_id, - &(&content).into(), + &content, ); - let content: OutgoingContent = alice_request.accept().unwrap().into(); + let content: OutgoingContent = alice_request.accept().unwrap().try_into().unwrap(); let content = ReadyContent::try_from(&content).unwrap(); - bob_request.receive_ready(&alice_id(), &content).unwrap(); + bob_request.receive_ready(&alice_id(), &content); assert!(bob_request.is_ready()); assert!(alice_request.is_ready()); - let (bob_sas, start_content) = bob_request.start(alice_device, None).unwrap(); + let (bob_sas, request) = bob_request.start_sas().await.unwrap().unwrap(); - let content = StartContent::try_from(&start_content).unwrap(); + let content: OutgoingContent = request.try_into().unwrap(); + let content = StartContent::try_from(&content).unwrap(); let flow_id = content.flow_id().to_owned(); alice_request.receive_start(bob_device.user_id(), &content).await.unwrap(); - let alice_sas = alice_request.verification_cache.get_sas(&flow_id).unwrap(); + let alice_sas = + alice_request.verification_cache.get_sas(bob_device.user_id(), &flow_id).unwrap(); assert!(!bob_sas.is_cancelled()); assert!(!alice_sas.is_cancelled()); diff --git a/matrix_sdk_crypto/src/verification/sas/helpers.rs b/matrix_sdk_crypto/src/verification/sas/helpers.rs index 4d4794e5..f39869b0 100644 --- a/matrix_sdk_crypto/src/verification/sas/helpers.rs +++ b/matrix_sdk_crypto/src/verification/sas/helpers.rs @@ -14,17 +14,15 @@ use std::{collections::BTreeMap, convert::TryInto}; -use matrix_sdk_common::uuid::Uuid; use olm_rs::sas::OlmSas; use ruma::{ - api::client::r0::to_device::DeviceIdOrAllDevices, events::{ key::verification::{ cancel::CancelCode, mac::{MacEventContent, MacToDeviceEventContent}, Relation, }, - AnyMessageEventContent, AnyToDeviceEventContent, EventType, + AnyMessageEventContent, AnyToDeviceEventContent, }, DeviceKeyAlgorithm, DeviceKeyId, UserId, }; @@ -33,17 +31,17 @@ use tracing::{trace, warn}; use super::{FlowId, OutgoingContent}; use crate::{ - identities::{ReadOnlyDevice, UserIdentities}, + identities::{ReadOnlyDevice, ReadOnlyUserIdentities}, utilities::encode, verification::event_enums::{MacContent, StartContent}, - ReadOnlyAccount, ToDeviceRequest, + ReadOnlyAccount, }; #[derive(Clone, Debug)] pub struct SasIds { pub account: ReadOnlyAccount, pub other_device: ReadOnlyDevice, - pub other_identity: Option, + pub other_identity: Option, } /// Calculate the commitment for a accept event from the public key and the @@ -184,7 +182,7 @@ pub fn receive_mac_event( flow_id: &str, sender: &UserId, content: &MacContent, -) -> Result<(Vec, Vec), CancelCode> { +) -> Result<(Vec, Vec), CancelCode> { let mut verified_devices = Vec::new(); let mut verified_identities = Vec::new(); @@ -527,34 +525,6 @@ fn bytes_to_decimal(bytes: Vec) -> (u16, u16, u16) { (first + 1000, second + 1000, third + 1000) } -pub fn content_to_request( - recipient: &UserId, - recipient_device: impl Into, - content: AnyToDeviceEventContent, -) -> ToDeviceRequest { - let mut messages = BTreeMap::new(); - let mut user_messages = BTreeMap::new(); - - user_messages.insert( - recipient_device.into(), - serde_json::value::to_raw_value(&content).expect("Can't serialize to-device content"), - ); - messages.insert(recipient.clone(), user_messages); - - let event_type = match content { - AnyToDeviceEventContent::KeyVerificationAccept(_) => EventType::KeyVerificationAccept, - AnyToDeviceEventContent::KeyVerificationStart(_) => EventType::KeyVerificationStart, - AnyToDeviceEventContent::KeyVerificationKey(_) => EventType::KeyVerificationKey, - AnyToDeviceEventContent::KeyVerificationMac(_) => EventType::KeyVerificationMac, - AnyToDeviceEventContent::KeyVerificationCancel(_) => EventType::KeyVerificationCancel, - AnyToDeviceEventContent::KeyVerificationReady(_) => EventType::KeyVerificationReady, - AnyToDeviceEventContent::KeyVerificationDone(_) => EventType::KeyVerificationDone, - _ => unreachable!(), - }; - - ToDeviceRequest { txn_id: Uuid::new_v4(), event_type, messages } -} - #[cfg(test)] mod test { use proptest::prelude::*; diff --git a/matrix_sdk_crypto/src/verification/sas/inner_sas.rs b/matrix_sdk_crypto/src/verification/sas/inner_sas.rs index b9b37475..53677e33 100644 --- a/matrix_sdk_crypto/src/verification/sas/inner_sas.rs +++ b/matrix_sdk_crypto/src/verification/sas/inner_sas.rs @@ -24,11 +24,12 @@ use ruma::{ use super::{ sas_state::{ Accepted, Confirmed, Created, KeyReceived, MacReceived, SasState, Started, WaitingForDone, + WeAccepted, }, FlowId, }; use crate::{ - identities::{ReadOnlyDevice, UserIdentities}, + identities::{ReadOnlyDevice, ReadOnlyUserIdentities}, verification::{ event_enums::{AnyVerificationContent, OutgoingContent, OwnedAcceptContent, StartContent}, Cancelled, Done, @@ -41,6 +42,7 @@ pub enum InnerSas { Created(SasState), Started(SasState), Accepted(SasState), + WeAccepted(SasState), KeyReceived(SasState), Confirmed(SasState), MacReceived(SasState), @@ -53,7 +55,7 @@ impl InnerSas { pub fn start( account: ReadOnlyAccount, other_device: ReadOnlyDevice, - other_identity: Option, + other_identity: Option, transaction_id: Option, ) -> (InnerSas, OutgoingContent) { let sas = SasState::::new(account, other_device, other_identity, transaction_id); @@ -61,6 +63,34 @@ impl InnerSas { (InnerSas::Created(sas), content.into()) } + pub fn started_from_request(&self) -> bool { + match self { + InnerSas::Created(s) => s.started_from_request, + InnerSas::Started(s) => s.started_from_request, + InnerSas::WeAccepted(s) => s.started_from_request, + InnerSas::Accepted(s) => s.started_from_request, + InnerSas::KeyReceived(s) => s.started_from_request, + InnerSas::Confirmed(s) => s.started_from_request, + InnerSas::MacReceived(s) => s.started_from_request, + InnerSas::WaitingForDone(s) => s.started_from_request, + InnerSas::Done(s) => s.started_from_request, + InnerSas::Cancelled(s) => s.started_from_request, + } + } + + pub fn has_been_accepted(&self) -> bool { + match self { + InnerSas::Created(_) | InnerSas::Started(_) | InnerSas::Cancelled(_) => false, + InnerSas::Accepted(_) + | InnerSas::WeAccepted(_) + | InnerSas::KeyReceived(_) + | InnerSas::Confirmed(_) + | InnerSas::MacReceived(_) + | InnerSas::WaitingForDone(_) + | InnerSas::Done(_) => true, + } + } + pub fn supports_emoji(&self) -> bool { match self { InnerSas::Created(_) => false, @@ -69,6 +99,11 @@ impl InnerSas { .accepted_protocols .short_auth_string .contains(&ShortAuthenticationString::Emoji), + InnerSas::WeAccepted(s) => s + .state + .accepted_protocols + .short_auth_string + .contains(&ShortAuthenticationString::Emoji), InnerSas::Accepted(s) => s .state .accepted_protocols @@ -96,7 +131,7 @@ impl InnerSas { room_id: RoomId, account: ReadOnlyAccount, other_device: ReadOnlyDevice, - other_identity: Option, + other_identity: Option, ) -> (InnerSas, OutgoingContent) { let sas = SasState::::new_in_room( room_id, @@ -114,7 +149,7 @@ impl InnerSas { other_device: ReadOnlyDevice, flow_id: FlowId, content: &StartContent, - other_identity: Option, + other_identity: Option, started_from_request: bool, ) -> Result { match SasState::::from_start_event( @@ -130,9 +165,14 @@ impl InnerSas { } } - pub fn accept(&self) -> Option { + pub fn accept( + self, + methods: Vec, + ) -> Option<(InnerSas, OwnedAcceptContent)> { if let InnerSas::Started(s) = self { - Some(s.as_content()) + let sas = s.into_accepted(methods); + let content = sas.as_content(); + Some((InnerSas::WeAccepted(sas), content)) } else { None } @@ -151,17 +191,25 @@ impl InnerSas { InnerSas::MacReceived(s) => s.set_creation_time(time), InnerSas::Done(s) => s.set_creation_time(time), InnerSas::WaitingForDone(s) => s.set_creation_time(time), + InnerSas::WeAccepted(s) => s.set_creation_time(time), } } - pub fn cancel(self, code: CancelCode) -> (InnerSas, Option) { + pub fn cancel( + self, + cancelled_by_us: bool, + code: CancelCode, + ) -> (InnerSas, Option) { let sas = match self { - InnerSas::Created(s) => s.cancel(code), - InnerSas::Started(s) => s.cancel(code), - InnerSas::Accepted(s) => s.cancel(code), - InnerSas::KeyReceived(s) => s.cancel(code), - InnerSas::MacReceived(s) => s.cancel(code), - _ => return (self, None), + InnerSas::Created(s) => s.cancel(cancelled_by_us, code), + InnerSas::Started(s) => s.cancel(cancelled_by_us, code), + InnerSas::Accepted(s) => s.cancel(cancelled_by_us, code), + InnerSas::WeAccepted(s) => s.cancel(cancelled_by_us, code), + InnerSas::KeyReceived(s) => s.cancel(cancelled_by_us, code), + InnerSas::MacReceived(s) => s.cancel(cancelled_by_us, code), + InnerSas::Confirmed(s) => s.cancel(cancelled_by_us, code), + InnerSas::WaitingForDone(s) => s.cancel(cancelled_by_us, code), + InnerSas::Done(_) | InnerSas::Cancelled(_) => return (self, None), }; let content = sas.as_content(); @@ -216,7 +264,7 @@ impl InnerSas { } } AnyVerificationContent::Cancel(c) => { - let (sas, _) = self.cancel(c.cancel_code().to_owned()); + let (sas, _) = self.cancel(false, c.cancel_code().to_owned()); (sas, None) } AnyVerificationContent::Key(c) => match self { @@ -227,7 +275,7 @@ impl InnerSas { (InnerSas::Cancelled(s), Some(content)) } }, - InnerSas::Started(s) => match s.into_key_received(sender, c) { + InnerSas::WeAccepted(s) => match s.into_key_received(sender, c) { Ok(s) => { let content = s.as_content(); (InnerSas::KeyReceived(s), Some(content)) @@ -298,6 +346,10 @@ impl InnerSas { matches!(self, InnerSas::Cancelled(_)) } + pub fn have_we_confirmed(&self) -> bool { + matches!(self, InnerSas::Confirmed(_) | InnerSas::WaitingForDone(_) | InnerSas::Done(_)) + } + pub fn timed_out(&self) -> bool { match self { InnerSas::Created(s) => s.timed_out(), @@ -309,6 +361,7 @@ impl InnerSas { InnerSas::MacReceived(s) => s.timed_out(), InnerSas::WaitingForDone(s) => s.timed_out(), InnerSas::Done(s) => s.timed_out(), + InnerSas::WeAccepted(s) => s.timed_out(), } } @@ -323,6 +376,7 @@ impl InnerSas { InnerSas::MacReceived(s) => s.verification_flow_id.clone(), InnerSas::WaitingForDone(s) => s.verification_flow_id.clone(), InnerSas::Done(s) => s.verification_flow_id.clone(), + InnerSas::WeAccepted(s) => s.verification_flow_id.clone(), } } @@ -358,7 +412,7 @@ impl InnerSas { } } - pub fn verified_identities(&self) -> Option> { + pub fn verified_identities(&self) -> Option> { if let InnerSas::Done(s) = self { Some(s.verified_identities()) } else { diff --git a/matrix_sdk_crypto/src/verification/sas/mod.rs b/matrix_sdk_crypto/src/verification/sas/mod.rs index d8b0330f..d4b06949 100644 --- a/matrix_sdk_crypto/src/verification/sas/mod.rs +++ b/matrix_sdk_crypto/src/verification/sas/mod.rs @@ -20,17 +20,12 @@ use std::sync::{Arc, Mutex}; #[cfg(test)] use std::time::Instant; -pub use helpers::content_to_request; use inner_sas::InnerSas; use matrix_sdk_common::uuid::Uuid; use ruma::{ api::client::r0::keys::upload_signatures::Request as SignatureUploadRequest, events::{ - key::verification::{ - accept::{AcceptEventContent, AcceptMethod, AcceptToDeviceEventContent}, - cancel::CancelCode, - ShortAuthenticationString, - }, + key::verification::{cancel::CancelCode, ShortAuthenticationString}, AnyMessageEventContent, AnyToDeviceEventContent, }, DeviceId, EventId, RoomId, UserId, @@ -39,23 +34,26 @@ use tracing::trace; use super::{ event_enums::{AnyVerificationContent, OutgoingContent, OwnedAcceptContent, StartContent}, - FlowId, IdentitiesBeingVerified, VerificationResult, + requests::RequestHandle, + CancelInfo, FlowId, IdentitiesBeingVerified, VerificationResult, }; use crate::{ - identities::{ReadOnlyDevice, UserIdentities}, + identities::{ReadOnlyDevice, ReadOnlyUserIdentities}, olm::PrivateCrossSigningIdentity, requests::{OutgoingVerificationRequest, RoomMessageRequest}, store::{CryptoStore, CryptoStoreError}, ReadOnlyAccount, ToDeviceRequest, }; -#[derive(Clone, Debug)] /// Short authentication string object. +#[derive(Clone, Debug)] pub struct Sas { inner: Arc>, account: ReadOnlyAccount, identities_being_verified: IdentitiesBeingVerified, flow_id: Arc, + we_started: bool, + request_handle: Option, } impl Sas { @@ -89,25 +87,72 @@ impl Sas { &self.flow_id } + /// Get the room id if the verification is happening inside a room. + pub fn room_id(&self) -> Option<&RoomId> { + if let FlowId::InRoom(r, _) = self.flow_id() { + Some(r) + } else { + None + } + } + /// Does this verification flow support displaying emoji for the short /// authentication string. pub fn supports_emoji(&self) -> bool { self.inner.lock().unwrap().supports_emoji() } + /// Did this verification flow start from a verification request. + pub fn started_from_request(&self) -> bool { + self.inner.lock().unwrap().started_from_request() + } + + /// Is this a verification that is veryfying one of our own devices. + pub fn is_self_verification(&self) -> bool { + self.identities_being_verified.is_self_verification() + } + + /// Have we confirmed that the short auth string matches. + pub fn have_we_confirmed(&self) -> bool { + self.inner.lock().unwrap().have_we_confirmed() + } + + /// Has the verification been accepted by both parties. + pub fn has_been_accepted(&self) -> bool { + self.inner.lock().unwrap().has_been_accepted() + } + + /// Get info about the cancellation if the verification flow has been + /// cancelled. + pub fn cancel_info(&self) -> Option { + if let InnerSas::Cancelled(c) = &*self.inner.lock().unwrap() { + Some(c.state.as_ref().clone().into()) + } else { + None + } + } + + /// Did we initiate the verification flow. + pub fn we_started(&self) -> bool { + self.we_started + } + #[cfg(test)] #[allow(dead_code)] pub(crate) fn set_creation_time(&self, time: Instant) { self.inner.lock().unwrap().set_creation_time(time) } + #[allow(clippy::too_many_arguments)] fn start_helper( inner_sas: InnerSas, account: ReadOnlyAccount, private_identity: PrivateCrossSigningIdentity, other_device: ReadOnlyDevice, store: Arc, - other_identity: Option, + other_identity: Option, + we_started: bool, + request_handle: Option, ) -> Sas { let flow_id = inner_sas.verification_flow_id(); @@ -123,6 +168,8 @@ impl Sas { account, identities_being_verified: identities, flow_id, + we_started, + request_handle, } } @@ -136,13 +183,16 @@ impl Sas { /// /// Returns the new `Sas` object and a `StartEventContent` that needs to be /// sent out through the server to the other device. + #[allow(clippy::too_many_arguments)] pub(crate) fn start( account: ReadOnlyAccount, private_identity: PrivateCrossSigningIdentity, other_device: ReadOnlyDevice, store: Arc, - other_identity: Option, + other_identity: Option, transaction_id: Option, + we_started: bool, + request_handle: Option, ) -> (Sas, OutgoingContent) { let (inner, content) = InnerSas::start( account.clone(), @@ -159,6 +209,8 @@ impl Sas { other_device, store, other_identity, + we_started, + request_handle, ), content, ) @@ -174,6 +226,7 @@ impl Sas { /// /// Returns the new `Sas` object and a `StartEventContent` that needs to be /// sent out through the server to the other device. + #[allow(clippy::too_many_arguments)] pub(crate) fn start_in_room( flow_id: EventId, room_id: RoomId, @@ -181,7 +234,9 @@ impl Sas { private_identity: PrivateCrossSigningIdentity, other_device: ReadOnlyDevice, store: Arc, - other_identity: Option, + other_identity: Option, + we_started: bool, + request_handle: RequestHandle, ) -> (Sas, OutgoingContent) { let (inner, content) = InnerSas::start_in_room( flow_id, @@ -199,6 +254,8 @@ impl Sas { other_device, store, other_identity, + we_started, + Some(request_handle), ), content, ) @@ -222,8 +279,9 @@ impl Sas { account: ReadOnlyAccount, private_identity: PrivateCrossSigningIdentity, other_device: ReadOnlyDevice, - other_identity: Option, - started_from_request: bool, + other_identity: Option, + request_handle: Option, + we_started: bool, ) -> Result { let inner = InnerSas::from_start_event( account.clone(), @@ -231,7 +289,7 @@ impl Sas { flow_id, content, other_identity.clone(), - started_from_request, + request_handle.is_some(), )?; Ok(Self::start_helper( @@ -241,6 +299,8 @@ impl Sas { other_device, store, other_identity, + we_started, + request_handle, )) } @@ -262,18 +322,28 @@ impl Sas { &self, settings: AcceptSettings, ) -> Option { - self.inner.lock().unwrap().accept().map(|c| match settings.apply(c) { - OwnedAcceptContent::ToDevice(c) => { - let content = AnyToDeviceEventContent::KeyVerificationAccept(c); - self.content_to_request(content).into() - } - OwnedAcceptContent::Room(room_id, content) => RoomMessageRequest { - room_id, - txn_id: Uuid::new_v4(), - content: AnyMessageEventContent::KeyVerificationAccept(content), - } - .into(), - }) + let mut guard = self.inner.lock().unwrap(); + let sas: InnerSas = (*guard).clone(); + let methods = settings.allowed_methods; + + if let Some((sas, content)) = sas.accept(methods) { + *guard = sas; + + Some(match content { + OwnedAcceptContent::ToDevice(c) => { + let content = AnyToDeviceEventContent::KeyVerificationAccept(c); + self.content_to_request(content).into() + } + OwnedAcceptContent::Room(room_id, content) => RoomMessageRequest { + room_id, + txn_id: Uuid::new_v4(), + content: AnyMessageEventContent::KeyVerificationAccept(content), + } + .into(), + }) + } else { + None + } } /// Confirm the Sas verification. @@ -340,10 +410,28 @@ impl Sas { self.cancel_with_code(CancelCode::User) } - pub(crate) fn cancel_with_code(&self, code: CancelCode) -> Option { + /// Cancel the verification. + /// + /// This cancels the verification with given `CancelCode`. + /// + /// **Note**: This method should generally not be used, the [`cancel()`] + /// method should be preferred. The SDK will automatically cancel with the + /// approprate cancel code, user initiated cancellations should only cancel + /// with the `CancelCode::User` + /// + /// Returns None if the `Sas` object is already in a canceled state, + /// otherwise it returns a request that needs to be sent out. + /// + /// [`cancel()`]: #method.cancel + pub fn cancel_with_code(&self, code: CancelCode) -> Option { let mut guard = self.inner.lock().unwrap(); + + if let Some(request) = &self.request_handle { + request.cancel_with_code(&code) + } + let sas: InnerSas = (*guard).clone(); - let (sas, content) = sas.cancel(code); + let (sas, content) = sas.cancel(true, code); *guard = sas; content.map(|c| match c { OutgoingContent::Room(room_id, content) => { @@ -427,12 +515,12 @@ impl Sas { self.inner.lock().unwrap().verified_devices() } - pub(crate) fn verified_identities(&self) -> Option> { + pub(crate) fn verified_identities(&self) -> Option> { self.inner.lock().unwrap().verified_identities() } pub(crate) fn content_to_request(&self, content: AnyToDeviceEventContent) -> ToDeviceRequest { - content_to_request(self.other_user_id(), self.other_device_id().to_owned(), content) + ToDeviceRequest::new(self.other_user_id(), self.other_device_id().to_owned(), content) } } @@ -463,23 +551,6 @@ impl AcceptSettings { pub fn with_allowed_methods(methods: Vec) -> Self { Self { allowed_methods: methods } } - - fn apply(self, mut content: OwnedAcceptContent) -> OwnedAcceptContent { - match &mut content { - OwnedAcceptContent::ToDevice(AcceptToDeviceEventContent { - method: AcceptMethod::MSasV1(c), - .. - }) - | OwnedAcceptContent::Room( - _, - AcceptEventContent { method: AcceptMethod::MSasV1(c), .. }, - ) => { - c.short_authentication_string.retain(|sas| self.allowed_methods.contains(sas)); - content - } - _ => content, - } - } } #[cfg(test)] @@ -536,6 +607,8 @@ mod test { alice_store, None, None, + true, + None, ); let flow_id = alice.flow_id().to_owned(); @@ -549,6 +622,7 @@ mod test { PrivateCrossSigningIdentity::empty(bob_id()), alice_device, None, + None, false, ) .unwrap(); diff --git a/matrix_sdk_crypto/src/verification/sas/sas_state.rs b/matrix_sdk_crypto/src/verification/sas/sas_state.rs index f3d82488..7aa9bc2f 100644 --- a/matrix_sdk_crypto/src/verification/sas/sas_state.rs +++ b/matrix_sdk_crypto/src/verification/sas/sas_state.rs @@ -13,7 +13,7 @@ // limitations under the License. use std::{ - convert::TryFrom, + convert::{TryFrom, TryInto}, matches, sync::{Arc, Mutex}, time::{Duration, Instant}, @@ -52,7 +52,7 @@ use super::{ OutgoingContent, }; use crate::{ - identities::{ReadOnlyDevice, UserIdentities}, + identities::{ReadOnlyDevice, ReadOnlyUserIdentities}, verification::{ event_enums::{ AcceptContent, DoneContent, KeyContent, MacContent, OwnedAcceptContent, @@ -102,7 +102,7 @@ impl TryFrom for AcceptedProtocols { Err(CancelCode::UnknownMethod) } else { Ok(Self { - method: VerificationMethod::MSasV1, + method: VerificationMethod::SasV1, hash: content.hash, key_agreement_protocol: content.key_agreement_protocol, message_auth_code: content.message_authentication_code, @@ -149,7 +149,7 @@ impl TryFrom<&SasV1Content> for AcceptedProtocols { } Ok(Self { - method: VerificationMethod::MSasV1, + method: VerificationMethod::SasV1, hash: HashAlgorithm::Sha256, key_agreement_protocol: KeyAgreementProtocol::Curve25519HkdfSha256, message_auth_code: MessageAuthenticationCode::HkdfHmacSha256, @@ -163,7 +163,7 @@ impl TryFrom<&SasV1Content> for AcceptedProtocols { impl Default for AcceptedProtocols { fn default() -> Self { AcceptedProtocols { - method: VerificationMethod::MSasV1, + method: VerificationMethod::SasV1, hash: HashAlgorithm::Sha256, key_agreement_protocol: KeyAgreementProtocol::Curve25519HkdfSha256, message_auth_code: MessageAuthenticationCode::HkdfHmacSha256, @@ -222,7 +222,7 @@ impl std::fmt::Debug for SasState { /// The initial SAS state. #[derive(Clone, Debug)] pub struct Created { - protocol_definitions: SasV1ContentInit, + protocol_definitions: SasV1Content, } /// The initial SAS state if the other side started the SAS verification. @@ -241,6 +241,15 @@ pub struct Accepted { commitment: String, } +/// The SAS state we're going to be in after we accepted our +/// verification start event. +#[derive(Clone, Debug)] +pub struct WeAccepted { + we_started: bool, + pub accepted_protocols: Arc, + commitment: String, +} + /// The SAS state we're going to be in after we received the public key of the /// other participant. /// @@ -268,7 +277,7 @@ pub struct MacReceived { we_started: bool, their_pubkey: String, verified_devices: Arc<[ReadOnlyDevice]>, - verified_master_keys: Arc<[UserIdentities]>, + verified_master_keys: Arc<[ReadOnlyUserIdentities]>, pub accepted_protocols: Arc, } @@ -278,7 +287,7 @@ pub struct MacReceived { #[derive(Clone, Debug)] pub struct WaitingForDone { verified_devices: Arc<[ReadOnlyDevice]>, - verified_master_keys: Arc<[UserIdentities]>, + verified_master_keys: Arc<[ReadOnlyUserIdentities]>, } impl SasState { @@ -298,14 +307,14 @@ impl SasState { self.ids.other_device.clone() } - pub fn cancel(self, cancel_code: CancelCode) -> SasState { + pub fn cancel(self, cancelled_by_us: bool, cancel_code: CancelCode) -> SasState { SasState { inner: self.inner, ids: self.ids, creation_time: self.creation_time, last_event_time: self.last_event_time, verification_flow_id: self.verification_flow_id, - state: Arc::new(Cancelled::new(cancel_code)), + state: Arc::new(Cancelled::new(cancelled_by_us, cancel_code)), started_from_request: self.started_from_request, } } @@ -353,7 +362,7 @@ impl SasState { pub fn new( account: ReadOnlyAccount, other_device: ReadOnlyDevice, - other_identity: Option, + other_identity: Option, transaction_id: Option, ) -> SasState { let started_from_request = transaction_id.is_some(); @@ -379,7 +388,7 @@ impl SasState { event_id: EventId, account: ReadOnlyAccount, other_device: ReadOnlyDevice, - other_identity: Option, + other_identity: Option, ) -> SasState { let flow_id = FlowId::InRoom(room_id, event_id); Self::new_helper(flow_id, account, other_device, other_identity, false) @@ -389,7 +398,7 @@ impl SasState { flow_id: FlowId, account: ReadOnlyAccount, other_device: ReadOnlyDevice, - other_identity: Option, + other_identity: Option, started_from_request: bool, ) -> SasState { SasState { @@ -407,7 +416,9 @@ impl SasState { key_agreement_protocols: KEY_AGREEMENT_PROTOCOLS.to_vec(), message_authentication_codes: MACS.to_vec(), hashes: HASHES.to_vec(), - }, + } + .try_into() + .expect("Invalid protocol definition."), }), } } @@ -417,19 +428,13 @@ impl SasState { FlowId::ToDevice(s) => OwnedStartContent::ToDevice(StartToDeviceEventContent::new( self.device_id().into(), s.to_string(), - StartMethod::SasV1( - SasV1Content::new(self.state.protocol_definitions.clone()) - .expect("Invalid initial protocol definitions."), - ), + StartMethod::SasV1(self.state.protocol_definitions.clone()), )), FlowId::InRoom(r, e) => OwnedStartContent::Room( r.clone(), StartEventContent::new( self.device_id().into(), - StartMethod::SasV1( - SasV1Content::new(self.state.protocol_definitions.clone()) - .expect("Invalid initial protocol definitions."), - ), + StartMethod::SasV1(self.state.protocol_definitions.clone()), Relation::new(e.clone()), ), ), @@ -448,11 +453,11 @@ impl SasState { sender: &UserId, content: &AcceptContent, ) -> Result, SasState> { - self.check_event(sender, content.flow_id()).map_err(|c| self.clone().cancel(c))?; + self.check_event(sender, content.flow_id()).map_err(|c| self.clone().cancel(true, c))?; - if let AcceptMethod::MSasV1(content) = content.method() { - let accepted_protocols = - AcceptedProtocols::try_from(content.clone()).map_err(|c| self.clone().cancel(c))?; + if let AcceptMethod::SasV1(content) = content.method() { + let accepted_protocols = AcceptedProtocols::try_from(content.clone()) + .map_err(|c| self.clone().cancel(true, c))?; let start_content = self.as_content().into(); @@ -461,7 +466,7 @@ impl SasState { ids: self.ids, verification_flow_id: self.verification_flow_id, creation_time: self.creation_time, - last_event_time: self.last_event_time, + last_event_time: Instant::now().into(), started_from_request: self.started_from_request, state: Arc::new(Accepted { start_content, @@ -470,7 +475,7 @@ impl SasState { }), }) } else { - Err(self.cancel(CancelCode::UnknownMethod)) + Err(self.cancel(true, CancelCode::UnknownMethod)) } } } @@ -492,7 +497,7 @@ impl SasState { pub fn from_start_event( account: ReadOnlyAccount, other_device: ReadOnlyDevice, - other_identity: Option, + other_identity: Option, flow_id: FlowId, content: &StartContent, started_from_request: bool, @@ -513,7 +518,7 @@ impl SasState { }, verification_flow_id: flow_id.clone(), - state: Arc::new(Cancelled::new(CancelCode::UnknownMethod)), + state: Arc::new(Cancelled::new(true, CancelCode::UnknownMethod)), }; if let StartMethod::SasV1(method_content) = content.method() { @@ -552,6 +557,32 @@ impl SasState { } } + pub fn into_accepted(self, methods: Vec) -> SasState { + let mut accepted_protocols = self.state.accepted_protocols.as_ref().to_owned(); + accepted_protocols.short_auth_string = methods; + + // Decimal is required per spec. + if !accepted_protocols.short_auth_string.contains(&ShortAuthenticationString::Decimal) { + accepted_protocols.short_auth_string.push(ShortAuthenticationString::Decimal); + } + + SasState { + inner: self.inner, + ids: self.ids, + verification_flow_id: self.verification_flow_id, + creation_time: self.creation_time, + last_event_time: self.last_event_time, + started_from_request: self.started_from_request, + state: Arc::new(WeAccepted { + we_started: false, + accepted_protocols: accepted_protocols.into(), + commitment: self.state.commitment.clone(), + }), + } + } +} + +impl SasState { /// Get the content for the accept event. /// /// The content needs to be sent to the other device. @@ -560,7 +591,7 @@ impl SasState { /// been started because of a /// m.key.verification.request -> m.key.verification.ready flow. pub fn as_content(&self) -> OwnedAcceptContent { - let method = AcceptMethod::MSasV1( + let method = AcceptMethod::SasV1( AcceptV1ContentInit { commitment: self.state.commitment.clone(), hash: self.state.accepted_protocols.hash.clone(), @@ -604,7 +635,7 @@ impl SasState { sender: &UserId, content: &KeyContent, ) -> Result, SasState> { - self.check_event(sender, content.flow_id()).map_err(|c| self.clone().cancel(c))?; + self.check_event(sender, content.flow_id()).map_err(|c| self.clone().cancel(true, c))?; let their_pubkey = content.public_key().to_owned(); @@ -619,7 +650,7 @@ impl SasState { ids: self.ids, verification_flow_id: self.verification_flow_id, creation_time: self.creation_time, - last_event_time: self.last_event_time, + last_event_time: Instant::now().into(), started_from_request: self.started_from_request, state: Arc::new(KeyReceived { we_started: false, @@ -644,7 +675,7 @@ impl SasState { sender: &UserId, content: &KeyContent, ) -> Result, SasState> { - self.check_event(sender, content.flow_id()).map_err(|c| self.clone().cancel(c))?; + self.check_event(sender, content.flow_id()).map_err(|c| self.clone().cancel(true, c))?; let commitment = calculate_commitment( content.public_key(), @@ -652,7 +683,7 @@ impl SasState { ); if self.state.commitment != commitment { - Err(self.cancel(CancelCode::InvalidMessage)) + Err(self.cancel(true, CancelCode::InvalidMessage)) } else { let their_pubkey = content.public_key().to_owned(); @@ -667,7 +698,7 @@ impl SasState { ids: self.ids, verification_flow_id: self.verification_flow_id, creation_time: self.creation_time, - last_event_time: self.last_event_time, + last_event_time: Instant::now().into(), started_from_request: self.started_from_request, state: Arc::new(KeyReceived { their_pubkey, @@ -684,10 +715,10 @@ impl SasState { pub fn as_content(&self) -> OutgoingContent { match &*self.verification_flow_id { FlowId::ToDevice(s) => { - AnyToDeviceEventContent::KeyVerificationKey(KeyToDeviceEventContent { - transaction_id: s.to_string(), - key: self.inner.lock().unwrap().public_key(), - }) + AnyToDeviceEventContent::KeyVerificationKey(KeyToDeviceEventContent::new( + s.to_string(), + self.inner.lock().unwrap().public_key(), + )) .into() } FlowId::InRoom(r, e) => ( @@ -710,10 +741,10 @@ impl SasState { pub fn as_content(&self) -> OutgoingContent { match &*self.verification_flow_id { FlowId::ToDevice(s) => { - AnyToDeviceEventContent::KeyVerificationKey(KeyToDeviceEventContent { - transaction_id: s.to_string(), - key: self.inner.lock().unwrap().public_key(), - }) + AnyToDeviceEventContent::KeyVerificationKey(KeyToDeviceEventContent::new( + s.to_string(), + self.inner.lock().unwrap().public_key(), + )) .into() } FlowId::InRoom(r, e) => ( @@ -781,7 +812,7 @@ impl SasState { sender: &UserId, content: &MacContent, ) -> Result, SasState> { - self.check_event(sender, content.flow_id()).map_err(|c| self.clone().cancel(c))?; + self.check_event(sender, content.flow_id()).map_err(|c| self.clone().cancel(true, c))?; let (devices, master_keys) = receive_mac_event( &self.inner.lock().unwrap(), @@ -790,13 +821,13 @@ impl SasState { sender, content, ) - .map_err(|c| self.clone().cancel(c))?; + .map_err(|c| self.clone().cancel(true, c))?; Ok(SasState { inner: self.inner, verification_flow_id: self.verification_flow_id, creation_time: self.creation_time, - last_event_time: self.last_event_time, + last_event_time: Instant::now().into(), ids: self.ids, started_from_request: self.started_from_request, state: Arc::new(MacReceived { @@ -841,7 +872,7 @@ impl SasState { sender: &UserId, content: &MacContent, ) -> Result, SasState> { - self.check_event(sender, content.flow_id()).map_err(|c| self.clone().cancel(c))?; + self.check_event(sender, content.flow_id()).map_err(|c| self.clone().cancel(true, c))?; let (devices, master_keys) = receive_mac_event( &self.inner.lock().unwrap(), @@ -850,12 +881,12 @@ impl SasState { sender, content, ) - .map_err(|c| self.clone().cancel(c))?; + .map_err(|c| self.clone().cancel(true, c))?; Ok(SasState { inner: self.inner, creation_time: self.creation_time, - last_event_time: self.last_event_time, + last_event_time: Instant::now().into(), verification_flow_id: self.verification_flow_id, started_from_request: self.started_from_request, ids: self.ids, @@ -881,7 +912,7 @@ impl SasState { sender: &UserId, content: &MacContent, ) -> Result, SasState> { - self.check_event(sender, content.flow_id()).map_err(|c| self.clone().cancel(c))?; + self.check_event(sender, content.flow_id()).map_err(|c| self.clone().cancel(true, c))?; let (devices, master_keys) = receive_mac_event( &self.inner.lock().unwrap(), @@ -890,12 +921,12 @@ impl SasState { sender, content, ) - .map_err(|c| self.clone().cancel(c))?; + .map_err(|c| self.clone().cancel(true, c))?; Ok(SasState { inner: self.inner, creation_time: self.creation_time, - last_event_time: self.last_event_time, + last_event_time: Instant::now().into(), verification_flow_id: self.verification_flow_id, started_from_request: self.started_from_request, ids: self.ids, @@ -1036,12 +1067,12 @@ impl SasState { sender: &UserId, content: &DoneContent, ) -> Result, SasState> { - self.check_event(sender, content.flow_id()).map_err(|c| self.clone().cancel(c))?; + self.check_event(sender, content.flow_id()).map_err(|c| self.clone().cancel(true, c))?; Ok(SasState { inner: self.inner, creation_time: self.creation_time, - last_event_time: self.last_event_time, + last_event_time: Instant::now().into(), verification_flow_id: self.verification_flow_id, started_from_request: self.started_from_request, ids: self.ids, @@ -1073,7 +1104,7 @@ impl SasState { } /// Get the list of verified identities. - pub fn verified_identities(&self) -> Arc<[UserIdentities]> { + pub fn verified_identities(&self) -> Arc<[ReadOnlyUserIdentities]> { self.state.verified_master_keys.clone() } } @@ -1090,13 +1121,15 @@ mod test { use ruma::{ events::key::verification::{ - accept::{AcceptMethod, CustomContent}, - start::{CustomContent as CustomStartContent, StartMethod}, + accept::{AcceptMethod, AcceptToDeviceEventContent}, + start::{StartMethod, StartToDeviceEventContent}, + ShortAuthenticationString, }, DeviceId, UserId, }; + use serde_json::json; - use super::{Accepted, Created, SasState, Started}; + use super::{Accepted, Created, SasState, Started, WeAccepted}; use crate::{ verification::event_enums::{AcceptContent, KeyContent, MacContent, StartContent}, ReadOnlyAccount, ReadOnlyDevice, @@ -1118,7 +1151,7 @@ mod test { "BOBDEVCIE".into() } - async fn get_sas_pair() -> (SasState, SasState) { + async fn get_sas_pair() -> (SasState, SasState) { let alice = ReadOnlyAccount::new(&alice_id(), &alice_device_id()); let alice_device = ReadOnlyDevice::from_account(&alice).await; @@ -1138,8 +1171,9 @@ mod test { &start_content.as_start_content(), false, ); + let bob_sas = bob_sas.unwrap().into_accepted(vec![ShortAuthenticationString::Emoji]); - (alice_sas, bob_sas.unwrap()) + (alice_sas, bob_sas) } #[tokio::test] @@ -1227,7 +1261,7 @@ mod test { let mut method = content.method_mut(); match &mut method { - AcceptMethod::MSasV1(ref mut c) => { + AcceptMethod::SasV1(ref mut c) => { c.commitment = "".to_string(); } _ => panic!("Unknown accept event content"), @@ -1266,7 +1300,7 @@ mod test { let mut method = content.method_mut(); match &mut method { - AcceptMethod::MSasV1(ref mut c) => { + AcceptMethod::SasV1(ref mut c) => { c.short_authentication_string = vec![]; } _ => panic!("Unknown accept event content"), @@ -1283,14 +1317,13 @@ mod test { async fn sas_unknown_method() { let (alice, bob) = get_sas_pair().await; - let mut content = bob.as_content(); - let method = content.method_mut(); - - *method = AcceptMethod::Custom(CustomContent { - method: "m.sas.custom".to_string(), - data: Default::default(), + let content = json!({ + "method": "m.sas.custom", + "method_data": "something", + "transaction_id": "some_id", }); + let content: AcceptToDeviceEventContent = serde_json::from_value(content).unwrap(); let content = AcceptContent::from(&content); alice @@ -1331,22 +1364,22 @@ mod test { ) .expect_err("Didn't cancel on invalid MAC method"); - let mut start_content = alice_sas.as_content(); - let method = start_content.method_mut(); - - *method = StartMethod::Custom(CustomStartContent { - method: "m.sas.custom".to_string(), - data: Default::default(), + let content = json!({ + "method": "m.sas.custom", + "from_device": "DEVICEID", + "method_data": "something", + "transaction_id": "some_id", }); - let flow_id = start_content.flow_id(); - let content = StartContent::from(&start_content); + let content: StartToDeviceEventContent = serde_json::from_value(content).unwrap(); + let content = StartContent::from(&content); + let flow_id = content.flow_id().to_owned(); SasState::::from_start_event( bob.clone(), alice_device, None, - flow_id, + flow_id.into(), &content, false, ) diff --git a/matrix_sdk_test/Cargo.toml b/matrix_sdk_test/Cargo.toml index f640d00f..6eaa9cc4 100644 --- a/matrix_sdk_test/Cargo.toml +++ b/matrix_sdk_test/Cargo.toml @@ -8,16 +8,16 @@ license = "Apache-2.0" name = "matrix-sdk-test" readme = "README.md" repository = "https://github.com/matrix-org/matrix-rust-sdk" -version = "0.2.0" +version = "0.3.0" [features] appservice = [] [dependencies] -http = "0.2.3" +http = "0.2.4" lazy_static = "1.4.0" -matrix-sdk-common = { version = "0.2.0", path = "../matrix_sdk_common" } +matrix-sdk-common = { version = "0.3.0", path = "../matrix_sdk_common" } matrix-sdk-test-macros = { version = "0.1.0", path = "../matrix_sdk_test_macros" } -ruma = { version = "0.1.2", features = ["client-api-c"] } -serde = "1.0.122" -serde_json = "1.0.61" +ruma = { version = "0.2.0", features = ["client-api-c"] } +serde = "1.0.126" +serde_json = "1.0.64" diff --git a/matrix_sdk_test/src/appservice.rs b/matrix_sdk_test/src/appservice.rs index 23d889b2..8853613a 100644 --- a/matrix_sdk_test/src/appservice.rs +++ b/matrix_sdk_test/src/appservice.rs @@ -1,6 +1,6 @@ use std::convert::TryFrom; -use ruma::{events::AnyRoomEvent, identifiers::room_id}; +use ruma::{events::AnyRoomEvent, room_id}; use serde_json::Value; use crate::{test_json, EventsJson};