Merge branch 'master' into room-state-getters

master
Damir Jelić 2021-07-27 11:18:29 +02:00
commit aa5f532f86
82 changed files with 4859 additions and 1989 deletions

2
.cargo/config.toml Normal file
View File

@ -0,0 +1,2 @@
[doc.extern-map.registries]
crates-io = "https://docs.rs/"

View File

@ -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

View File

@ -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' }}

View File

@ -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ć <poljar@termina.org.uk>"]
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"

62
matrix_qrcode/README.md Normal file
View File

@ -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::<Luma<u8>>().build();
Ok(())
}
```
## License
[Apache-2.0](https://www.apache.org/licenses/LICENSE-2.0)

View File

@ -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::<Luma<u8>>().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::<Luma<u8>>().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::<Luma<u8>>().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::<Luma<u8>>().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(_))))
}
}

View File

@ -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<DynamicImage> for QrVerification {
impl TryFrom<DynamicImage> for QrVerificationData {
type Error = DecodingError;
fn try_from(image: DynamicImage) -> Result<Self, Self::Error> {
@ -56,7 +56,7 @@ impl TryFrom<DynamicImage> for QrVerification {
#[cfg(feature = "decode_image")]
#[cfg_attr(feature = "docs", doc(cfg(decode_image)))]
impl TryFrom<ImageBuffer<Luma<u8>, Vec<u8>>> for QrVerification {
impl TryFrom<ImageBuffer<Luma<u8>, Vec<u8>>> for QrVerificationData {
type Error = DecodingError;
fn try_from(image: ImageBuffer<Luma<u8>, Vec<u8>>) -> Result<Self, Self::Error> {
@ -64,7 +64,7 @@ impl TryFrom<ImageBuffer<Luma<u8>, Vec<u8>>> for QrVerification {
}
}
impl TryFrom<&[u8]> for QrVerification {
impl TryFrom<&[u8]> for QrVerificationData {
type Error = DecodingError;
fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
@ -72,7 +72,7 @@ impl TryFrom<&[u8]> for QrVerification {
}
}
impl TryFrom<Vec<u8>> for QrVerification {
impl TryFrom<Vec<u8>> for QrVerificationData {
type Error = DecodingError;
fn try_from(value: Vec<u8>) -> Result<Self, Self::Error> {
@ -80,8 +80,8 @@ impl TryFrom<Vec<u8>> 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<QrCode, EncodingError> {
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<Vec<u8>, 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<Luma<u8>, Vec<u8>>) -> Result<QrVerification, DecodingError> {
fn decode(image: ImageBuffer<Luma<u8>, Vec<u8>>) -> Result<QrVerificationData, DecodingError> {
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<VerificationData> for QrVerification {
impl From<VerificationData> 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<SelfVerificationData> for QrVerification {
impl From<SelfVerificationData> 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<SelfVerificationNoMasterKey> for QrVerification {
impl From<SelfVerificationNoMasterKey> for QrVerificationData {
fn from(data: SelfVerificationNoMasterKey) -> Self {
Self::SelfVerificationNoMasterKey(data)
}

View File

@ -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"

View File

@ -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};

View File

@ -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;

View File

@ -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;

View File

@ -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;
}
}
}
_ => (),

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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 = "../.."

View File

@ -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<JsValue, JsValue> {
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)
}

View File

@ -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<u32>, height: Option<u32>) -> Result<Option<Vec<u8>>> {
// TODO: try to offer the avatar from cache, requires avatar cache
pub async fn avatar(&self, format: MediaFormat) -> Result<Option<Vec<u8>>> {
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<ToDeviceResponse> {
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<Sas> {
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<Verification> {
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<str>,
) -> Option<VerificationRequest> {
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)]

View File

@ -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<Sas> {
///
/// [`request_verification()`]: #method.request_verification
pub async fn start_verification(&self) -> Result<SasVerification> {
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<VerificationRequest> {
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<VerificationMethod>,
) -> Result<VerificationRequest> {
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.

View File

@ -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<SdkBaseError> 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<ReqwestError> for Error {
fn from(e: ReqwestError) -> Self {
Error::Http(HttpError::Reqwest(e))

View File

@ -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<AnySyncRoomEvent>,
) {
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::<SyncStateEvent<CustomEventContent>>()
{
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::<SyncMessageEvent<CustomEventContent>>()
{
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<AnySyncStateEvent>,
) {
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::<SyncStateEvent<CustomEventContent>>() {
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<MsgEventContent>) {}
/// Fires when `Client` receives a `RoomEvent::RoomMessageFeedback` event.
async fn on_room_message_feedback(&self, _: Room, _: &SyncMessageEvent<FeedbackEventContent>) {}
/// Fires when `Client` receives a `RoomEvent::Reaction` event.
async fn on_room_reaction(&self, _: Room, _: &SyncMessageEvent<ReactionEventContent>) {}
/// Fires when `Client` receives a `RoomEvent::CallInvite` event
async fn on_room_call_invite(&self, _: Room, _: &SyncMessageEvent<InviteEventContent>) {}
/// Fires when `Client` receives a `RoomEvent::CallAnswer` event

View File

@ -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<Client, HttpEr
#[cfg(not(target_arch = "wasm32"))]
let http_client = {
use http::HeaderValue;
let http_client = if config.disable_ssl_verification {
http_client.danger_accept_invalid_certs(true)
} else {
@ -303,6 +300,11 @@ async fn send_request(
request: http::Request<Bytes>,
config: RequestConfig,
) -> Result<http::Response<Bytes>, 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;

View File

@ -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");

View File

@ -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<u32>, height: Option<u32>) -> Result<Option<Vec<u8>>> {
// TODO: try to offer the avatar from cache, requires avatar cache
pub async fn avatar(&self, format: MediaFormat) -> Result<Option<Vec<u8>>> {
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

View File

@ -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();
/// # })

View File

@ -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<u32>, height: Option<u32>) -> Result<Option<Vec<u8>>> {
// TODO: try to offer the avatar from cache, requires avatar cache
pub async fn avatar(&self, format: MediaFormat) -> Result<Option<Vec<u8>>> {
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)
}

View File

@ -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<SasVerification> {
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<QrVerification> {
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<CancelInfo> {
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<SasVerification> for Verification {
fn from(sas: SasVerification) -> Self {
Self::SasV1(sas)
}
}
impl From<QrVerification> for Verification {
fn from(qr: QrVerification) -> Self {
Self::QrV1(qr)
}
}

View File

@ -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<CancelInfo> {
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<QrCode, EncodingError> {
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<Vec<u8>, 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(())
}
}

View File

@ -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<CancelInfo> {
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<Vec<VerificationMethod>> {
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<VerificationMethod>) -> 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<Option<QrVerification>> {
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<Option<SasVerification>> {
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(())
}
}

View File

@ -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<CancelInfo> {
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()
}
}

View File

@ -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!()
}
}

View File

@ -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"

View File

@ -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<MemberEventContent>) {
match self.handle_room_member(room, event).await {
Ok(_) => (),
@ -63,10 +65,10 @@ pub async fn main() -> Result<(), Box<dyn std::error::Error>> {
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?;

View File

@ -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 {}

View File

@ -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<T> = std::result::Result<T, Error>;
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<Registration> for AppserviceRegistration {
impl From<Registration> 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<AppserviceRegistration>,
registration: Arc<AppServiceRegistration>,
clients: Arc<DashMap<Localpart, Client>>,
}
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<Url, Error = url::ParseError>,
server_name: impl TryInto<ServerNameBox, Error = identifiers::Error>,
registration: AppserviceRegistration,
registration: AppServiceRegistration,
) -> Result<Self> {
let appservice = Self::new_with_config(
homeserver_url,
@ -235,7 +238,7 @@ impl Appservice {
pub async fn new_with_config(
homeserver_url: impl TryInto<Url, Error = url::ParseError>,
server_name: impl TryInto<ServerNameBox, Error = identifiers::Error>,
registration: AppserviceRegistration,
registration: AppServiceRegistration,
client_config: ClientConfig,
) -> Result<Self> {
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<str>) -> Result<bool> {
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<String>, port: impl Into<u16>) -> 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!()
}
}

View File

@ -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<String>,
port: impl Into<u16>,
) -> 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<api::event::push_events::v1::IncomingRequest>,
appservice: Data<Appservice>,
) -> Result<HttpResponse, Error> {
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<api::query::query_user_id::v1::IncomingRequest>,
appservice: Data<Appservice>,
) -> Result<HttpResponse, Error> {
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<api::query::query_room_alias::v1::IncomingRequest>,
appservice: Data<Appservice>,
) -> Result<HttpResponse, Error> {
if !appservice.compare_hs_token(request.access_token) {
return Ok(HttpResponse::Unauthorized().finish());
}
Ok(HttpResponse::Ok().json("{}"))
}
#[derive(Debug)]
pub struct IncomingRequest<T> {
access_token: String,
incoming: T,
}
impl<T: ruma::api::IncomingRequest> FromRequest for IncomingRequest<T> {
type Error = Error;
type Future = Pin<Box<dyn Future<Output = Result<Self, Self::Error>>>>;
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)?,
})
})
}
}

View File

@ -1,4 +1,2 @@
#[cfg(feature = "actix")]
pub mod actix;
#[cfg(feature = "warp")]
pub mod warp;

View File

@ -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<String>,
port: impl Into<u16>,
) -> 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<Bytes>)> {
fn common(appservice: AppService) -> BoxedFilter<(AppService, http::Request<Bytes>)> {
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<Bytes>,
) -> StdResult<impl warp::Reply, Rejection> {
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<Bytes>,
) -> StdResult<impl warp::Reply, Rejection> {
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<Bytes>,
) -> StdResult<impl warp::Reply, Rejection> {
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?;

View File

@ -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<Registration>) -> Result<Appservice> {
async fn appservice(registration: Option<Registration>) -> Result<AppService> {
// 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<Registration>) -> Result<Appservice> {
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(&registration_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");

View File

@ -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"

View File

@ -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},

View File

@ -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<Raw<MemberEventContent>>,
}
/// 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]: <https://github.com/matrix-org/matrix-doc/issues/684#issuecomment-641182668>
/// [discussion]: <https://github.com/matrix-org/matrix-doc/issues/684#issuecomment-641182668>
pub fn hoist_room_event_prev_content(
event: &Raw<AnySyncRoomEvent>,
) -> StdResult<AnySyncRoomEvent, serde_json::Error> {
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<Sas> {
self.olm.lock().await.as_ref().and_then(|o| o.get_verification(flow_id))
}
/// Get a specific device of a user.
///
/// # Arguments

View File

@ -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;

View File

@ -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};

View File

@ -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;

View File

@ -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"] }

View File

@ -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};

View File

@ -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"

View File

@ -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<u8>,
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<usize> {
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<String, String>,
@ -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<usize> {
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(),

View File

@ -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::<Hmac<Sha512>>(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::<Sha256>::new_varkey(hmac_key).expect("Can't create HMAC object");
let mut hmac = Hmac::<Sha256>::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<String, KeyExpor
pbkdf2::<Hmac<Sha512>>(passphrase.as_bytes(), &salt, rounds, &mut derived_keys);
let (key, hmac_key) = derived_keys.split_at(KEY_SIZE);
let mut hmac = Hmac::<Sha256>::new_varkey(hmac_key).expect("Can't create an HMAC object");
let mut hmac = Hmac::<Sha256>::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())?)

View File

@ -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<Mutex<PrivateCrossSigningIdentity>>,
pub(crate) verification_machine: VerificationMachine,
pub(crate) own_identity: Option<OwnUserIdentity>,
pub(crate) device_owner_identity: Option<UserIdentities>,
pub(crate) own_identity: Option<ReadOnlyOwnUserIdentity>,
pub(crate) device_owner_identity: Option<ReadOnlyUserIdentities>,
}
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<VerificationMethod>,
) -> (VerificationRequest, OutgoingVerificationRequest) {
self.request_verification_helper(Some(methods)).await
}
async fn request_verification_helper(
&self,
methods: Option<Vec<VerificationMethod>>,
) -> (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<Option<Arc<Mutex<Vec<Session>>>>> {
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<DeviceIdBox, ReadOnlyDevice>,
pub(crate) private_identity: Arc<Mutex<PrivateCrossSigningIdentity>>,
pub(crate) verification_machine: VerificationMachine,
pub(crate) own_identity: Option<OwnUserIdentity>,
pub(crate) device_owner_identity: Option<UserIdentities>,
pub(crate) own_identity: Option<ReadOnlyOwnUserIdentity>,
pub(crate) device_owner_identity: Option<ReadOnlyUserIdentities>,
}
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<OwnUserIdentity>,
device_owner: &Option<UserIdentities>,
own_identity: &Option<ReadOnlyOwnUserIdentity>,
device_owner: &Option<ReadOnlyUserIdentities>,
) -> 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<ReadOnlyOwnUserIdentity>,
device_owner: &Option<ReadOnlyUserIdentities>,
) -> 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))
}

View File

@ -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 {

View File

@ -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

View File

@ -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<OwnUserIdentity> {
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<UserIdentity> {
match self {
Self::Other(i) => Some(i),
_ => None,
}
}
}
impl From<OwnUserIdentity> for UserIdentities {
fn from(i: OwnUserIdentity) -> Self {
Self::Own(i)
}
}
impl From<UserIdentity> 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<VerificationMethod>,
) -> Result<(VerificationRequest, OutgoingVerificationRequest), CryptoStoreError> {
self.request_verification_helper(Some(methods)).await
}
async fn request_verification_helper(
&self,
methods: Option<Vec<VerificationMethod>>,
) -> Result<(VerificationRequest, OutgoingVerificationRequest), CryptoStoreError> {
let devices: Vec<DeviceIdBox> = 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<Vec<VerificationMethod>>,
) -> 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<Vec<VerificationMethod>>,
) -> 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<OwnUserIdentity> for UserIdentities {
fn from(identity: OwnUserIdentity) -> Self {
UserIdentities::Own(identity)
impl From<ReadOnlyOwnUserIdentity> for ReadOnlyUserIdentities {
fn from(identity: ReadOnlyOwnUserIdentity) -> Self {
ReadOnlyUserIdentities::Own(identity)
}
}
impl From<UserIdentity> for UserIdentities {
fn from(identity: UserIdentity) -> Self {
UserIdentities::Other(identity)
impl From<ReadOnlyUserIdentity> 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<UserId>,
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<UserId>,
master_key: MasterPubkey,
self_signing_key: SelfSigningPubkey,
@ -547,7 +725,7 @@ pub struct OwnUserIdentity {
verified: Arc<AtomicBool>,
}
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());
}
}

View File

@ -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<OutgoingRequest, serde_json::Error> {
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<OutgoingRequest, serde_json::Error> {
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<OutgoingRequest, serde_json::Error> {
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<UserId>,
@ -229,13 +217,14 @@ impl KeyRequestMachine {
/// Load stored outgoing requests that were not yet sent out.
async fn load_outgoing_requests(&self) -> Result<Vec<OutgoingRequest>, 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();

View File

@ -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,
};

View File

@ -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<Sas> {
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<Verification> {
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<str>,
) -> Option<VerificationRequest> {
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<DeviceKeyAlgorithm, UInt>) {
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<VerificationRequest> {
self.verification_machine.get_requests(user_id)
}
fn update_one_time_key_count(&self, key_count: &BTreeMap<DeviceKeyAlgorithm, UInt>) {
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<DeviceKeyAlgorithm, UInt>,
) -> OlmResult<ToDevice> {
// 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<Option<UserIdentities>> {
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<Arc<ToDeviceRequest>>) -> EncryptedEventContent {
let to_device_request = &requests[0];
let content: Raw<EncryptedEventContent> = 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());
}
}

View File

@ -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<DeviceKeyAlgorithm, UInt>) {
pub fn update_uploaded_key_count(&self, key_count: &BTreeMap<DeviceKeyAlgorithm, UInt>) {
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<AtomicI64>,
uploaded_signed_key_count: Arc<AtomicU64>,
}
/// 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<u64, ()> {
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 {

View File

@ -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());
}
}
}

View File

@ -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<Relation> = 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.

View File

@ -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());

View File

@ -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<EncryptedEventContent> {
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,
))
}

View File

@ -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<MasterPubkey> {
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<OwnUserIdentity, SignatureError> {
pub(crate) async fn to_public_identity(
&self,
) -> Result<ReadOnlyOwnUserIdentity, SignatureError> {
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<SignatureUploadRequest, SignatureError> {
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();

View File

@ -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<BTreeMap<UserId, BTreeMap<String, Value>>, SignatureError> {
let user_master: &CrossSigningKey = user.master_key().as_ref();
let signature = self.inner.sign_json(serde_json::to_value(user_master)?).await?;

View File

@ -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<UserId, BTreeMap<DeviceIdOrAllDevices, Box<RawJsonValue>>>,
pub messages: BTreeMap<UserId, BTreeMap<DeviceIdOrAllDevices, Raw<AnyToDeviceEventContent>>>,
}
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<DeviceIdOrAllDevices>,
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<DeviceIdBox>,
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<DeviceIdOrAllDevices>,
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,

View File

@ -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<Device>,
) -> OlmResult<(Uuid, ToDeviceRequest, Vec<Session>)> {
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<Device>,
content: Value,
content: AnyToDeviceEventContent,
outbound: OutboundGroupSession,
message_index: u32,
being_shared: Arc<DashMap<Uuid, OutboundGroupSession>>,
@ -417,10 +412,7 @@ impl GroupSessionManager {
users: impl Iterator<Item = &UserId>,
encryption_settings: impl Into<EncryptionSettings>,
) -> OlmResult<Vec<Arc<ToDeviceRequest>>> {
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() {

View File

@ -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);
}
}

View File

@ -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<DashSet<UserId>>,
olm_hashes: Arc<DashMap<String, DashSet<String>>>,
devices: DeviceStore,
identities: Arc<DashMap<UserId, UserIdentities>>,
identities: Arc<DashMap<UserId, ReadOnlyUserIdentities>>,
outgoing_key_requests: Arc<DashMap<Uuid, OutgoingKeyRequest>>,
key_requests_by_info: Arc<DashMap<String, Uuid>>,
}
@ -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<Option<UserIdentities>> {
async fn get_user_identity(&self, user_id: &UserId) -> Result<Option<ReadOnlyUserIdentities>> {
#[allow(clippy::map_clone)]
Ok(self.identities.get(user_id).map(|i| i.clone()))
}

View File

@ -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<UserIdentities>,
pub changed: Vec<UserIdentities>,
pub new: Vec<ReadOnlyUserIdentities>,
pub changed: Vec<ReadOnlyUserIdentities>,
}
#[derive(Debug, Clone, Default)]
@ -218,8 +218,8 @@ impl Store {
device_id: &DeviceId,
) -> Result<Option<Device>> {
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<Option<UserIdentities>> {
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<Option<UserIdentities>>;
async fn get_user_identity(&self, user_id: &UserId) -> Result<Option<ReadOnlyUserIdentities>>;
/// Check if a hash for an Olm message stored in the database.
async fn is_message_known(&self, message_hash: &OlmMessageHash) -> Result<bool>;

View File

@ -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<Option<UserIdentities>> {
async fn get_user_identity(&self, user_id: &UserId) -> Result<Option<ReadOnlyUserIdentities>> {
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;

View File

@ -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<DashMap<String, Verification>>,
verification: Arc<DashMap<UserId, DashMap<String, Verification>>>,
outgoing_requests: Arc<DashMap<Uuid, OutgoingRequest>>,
}
@ -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<Verification>) {
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<QrVerification> {
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<Verification> {
self.verification.get(sender).and_then(|m| m.get(flow_id).map(|v| v.clone()))
}
pub fn outgoing_requests(&self) -> Vec<OutgoingRequest> {
self.outgoing_requests.iter().map(|r| (*r).clone()).collect()
}
pub fn garbage_collect(&self) -> Vec<OutgoingRequest> {
self.verification.retain(|_, s| !(s.is_done() || s.is_cancelled()));
pub fn garbage_collect(&self) -> Vec<OutgoingVerificationRequest> {
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<OutgoingVerificationRequest> = 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<Sas> {
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<Sas> {
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()) };

View File

@ -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<AnyVerificationContent> {
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<Self, Self::Error> {
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<OutgoingVerificationRequest> for OutgoingContent {
fn from(request: OutgoingVerificationRequest) -> Self {
impl TryFrom<OutgoingVerificationRequest> for OutgoingContent {
type Error = String;
fn try_from(request: OutgoingVerificationRequest) -> Result<Self, Self::Error> {
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<RoomMessageRequest> for OutgoingContent {
fn from(value: RoomMessageRequest) -> Self {
(value.room_id, value.content).into()
}
}
#[cfg(test)]
impl TryFrom<ToDeviceRequest> for OutgoingContent {
type Error = ();
type Error = String;
fn try_from(value: ToDeviceRequest) -> Result<Self, Self::Error> {
fn try_from(request: ToDeviceRequest) -> Result<Self, Self::Error> {
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<OutgoingRequest> for OutgoingContent {
type Error = ();
type Error = String;
fn try_from(value: OutgoingRequest) -> Result<Self, ()> {
fn try_from(value: OutgoingRequest) -> Result<Self, Self::Error> {
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())),
}
}

View File

@ -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<Mutex<PrivateCrossSigningIdentity>>,
pub(crate) store: Arc<dyn CryptoStore>,
verifications: VerificationCache,
requests: Arc<DashMap<String, VerificationRequest>>,
requests: Arc<DashMap<UserId, DashMap<String, VerificationRequest>>>,
}
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<DeviceIdBox>,
methods: Option<Vec<VerificationMethod>>,
) -> (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<Vec<VerificationMethod>>,
) -> 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<str>) -> Option<VerificationRequest> {
self.requests.get(flow_id.as_ref()).map(|s| s.clone())
pub fn get_request(
&self,
user_id: &UserId,
flow_id: impl AsRef<str>,
) -> Option<VerificationRequest> {
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<Sas> {
self.verifications.get_sas(transaction_id)
pub fn get_requests(&self, user_id: &UserId) -> Vec<VerificationRequest> {
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<Verification> {
self.verifications.get(user_id, flow_id)
}
pub fn get_sas(&self, user_id: &UserId, flow_id: &str) -> Option<Sas> {
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<Raw<AnyToDeviceEvent>> {
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<OutgoingVerificationRequest> = self
.requests
.iter()
.flat_map(|v| {
let requests: Vec<OutgoingVerificationRequest> =
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());

View File

@ -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<Sas> {
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<QrVerification> {
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<Sas> for Verification {
}
}
impl From<QrVerification> 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<Sas> 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<Cancelled> 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<dyn CryptoStore>,
device_being_verified: ReadOnlyDevice,
identity_being_verified: Option<UserIdentities>,
identity_being_verified: Option<ReadOnlyUserIdentities>,
}
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<VerificationResult, CryptoStoreError> {
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<Option<UserIdentities>, CryptoStoreError> {
verified_identities: Option<&[ReadOnlyUserIdentities]>,
) -> Result<Option<ReadOnlyUserIdentities>, 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()
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -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<UserIdentities>,
pub other_identity: Option<ReadOnlyUserIdentities>,
}
/// 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<ReadOnlyDevice>, Vec<UserIdentities>), CancelCode> {
) -> Result<(Vec<ReadOnlyDevice>, Vec<ReadOnlyUserIdentities>), CancelCode> {
let mut verified_devices = Vec::new();
let mut verified_identities = Vec::new();
@ -527,34 +525,6 @@ fn bytes_to_decimal(bytes: Vec<u8>) -> (u16, u16, u16) {
(first + 1000, second + 1000, third + 1000)
}
pub fn content_to_request(
recipient: &UserId,
recipient_device: impl Into<DeviceIdOrAllDevices>,
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::*;

View File

@ -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<Created>),
Started(SasState<Started>),
Accepted(SasState<Accepted>),
WeAccepted(SasState<WeAccepted>),
KeyReceived(SasState<KeyReceived>),
Confirmed(SasState<Confirmed>),
MacReceived(SasState<MacReceived>),
@ -53,7 +55,7 @@ impl InnerSas {
pub fn start(
account: ReadOnlyAccount,
other_device: ReadOnlyDevice,
other_identity: Option<UserIdentities>,
other_identity: Option<ReadOnlyUserIdentities>,
transaction_id: Option<String>,
) -> (InnerSas, OutgoingContent) {
let sas = SasState::<Created>::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<UserIdentities>,
other_identity: Option<ReadOnlyUserIdentities>,
) -> (InnerSas, OutgoingContent) {
let sas = SasState::<Created>::new_in_room(
room_id,
@ -114,7 +149,7 @@ impl InnerSas {
other_device: ReadOnlyDevice,
flow_id: FlowId,
content: &StartContent,
other_identity: Option<UserIdentities>,
other_identity: Option<ReadOnlyUserIdentities>,
started_from_request: bool,
) -> Result<InnerSas, OutgoingContent> {
match SasState::<Started>::from_start_event(
@ -130,9 +165,14 @@ impl InnerSas {
}
}
pub fn accept(&self) -> Option<OwnedAcceptContent> {
pub fn accept(
self,
methods: Vec<ShortAuthenticationString>,
) -> 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<OutgoingContent>) {
pub fn cancel(
self,
cancelled_by_us: bool,
code: CancelCode,
) -> (InnerSas, Option<OutgoingContent>) {
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<Arc<[UserIdentities]>> {
pub fn verified_identities(&self) -> Option<Arc<[ReadOnlyUserIdentities]>> {
if let InnerSas::Done(s) = self {
Some(s.verified_identities())
} else {

View File

@ -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<Mutex<InnerSas>>,
account: ReadOnlyAccount,
identities_being_verified: IdentitiesBeingVerified,
flow_id: Arc<FlowId>,
we_started: bool,
request_handle: Option<RequestHandle>,
}
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<CancelInfo> {
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<dyn CryptoStore>,
other_identity: Option<UserIdentities>,
other_identity: Option<ReadOnlyUserIdentities>,
we_started: bool,
request_handle: Option<RequestHandle>,
) -> 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<dyn CryptoStore>,
other_identity: Option<UserIdentities>,
other_identity: Option<ReadOnlyUserIdentities>,
transaction_id: Option<String>,
we_started: bool,
request_handle: Option<RequestHandle>,
) -> (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<dyn CryptoStore>,
other_identity: Option<UserIdentities>,
other_identity: Option<ReadOnlyUserIdentities>,
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<UserIdentities>,
started_from_request: bool,
other_identity: Option<ReadOnlyUserIdentities>,
request_handle: Option<RequestHandle>,
we_started: bool,
) -> Result<Sas, OutgoingContent> {
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<OutgoingVerificationRequest> {
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<OutgoingVerificationRequest> {
/// 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<OutgoingVerificationRequest> {
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<Arc<[UserIdentities]>> {
pub(crate) fn verified_identities(&self) -> Option<Arc<[ReadOnlyUserIdentities]>> {
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<ShortAuthenticationString>) -> 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();

View File

@ -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<AcceptV1Content> 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<S: Clone + std::fmt::Debug> std::fmt::Debug for SasState<S> {
/// 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<AcceptedProtocols>,
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<AcceptedProtocols>,
}
@ -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<S: Clone> SasState<S> {
@ -298,14 +307,14 @@ impl<S: Clone> SasState<S> {
self.ids.other_device.clone()
}
pub fn cancel(self, cancel_code: CancelCode) -> SasState<Cancelled> {
pub fn cancel(self, cancelled_by_us: bool, cancel_code: CancelCode) -> SasState<Cancelled> {
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<Created> {
pub fn new(
account: ReadOnlyAccount,
other_device: ReadOnlyDevice,
other_identity: Option<UserIdentities>,
other_identity: Option<ReadOnlyUserIdentities>,
transaction_id: Option<String>,
) -> SasState<Created> {
let started_from_request = transaction_id.is_some();
@ -379,7 +388,7 @@ impl SasState<Created> {
event_id: EventId,
account: ReadOnlyAccount,
other_device: ReadOnlyDevice,
other_identity: Option<UserIdentities>,
other_identity: Option<ReadOnlyUserIdentities>,
) -> SasState<Created> {
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<Created> {
flow_id: FlowId,
account: ReadOnlyAccount,
other_device: ReadOnlyDevice,
other_identity: Option<UserIdentities>,
other_identity: Option<ReadOnlyUserIdentities>,
started_from_request: bool,
) -> SasState<Created> {
SasState {
@ -407,7 +416,9 @@ impl SasState<Created> {
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<Created> {
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<Created> {
sender: &UserId,
content: &AcceptContent,
) -> Result<SasState<Accepted>, SasState<Cancelled>> {
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<Created> {
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<Created> {
}),
})
} else {
Err(self.cancel(CancelCode::UnknownMethod))
Err(self.cancel(true, CancelCode::UnknownMethod))
}
}
}
@ -492,7 +497,7 @@ impl SasState<Started> {
pub fn from_start_event(
account: ReadOnlyAccount,
other_device: ReadOnlyDevice,
other_identity: Option<UserIdentities>,
other_identity: Option<ReadOnlyUserIdentities>,
flow_id: FlowId,
content: &StartContent,
started_from_request: bool,
@ -513,7 +518,7 @@ impl SasState<Started> {
},
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<Started> {
}
}
pub fn into_accepted(self, methods: Vec<ShortAuthenticationString>) -> SasState<WeAccepted> {
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<WeAccepted> {
/// Get the content for the accept event.
///
/// The content needs to be sent to the other device.
@ -560,7 +591,7 @@ impl SasState<Started> {
/// 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<Started> {
sender: &UserId,
content: &KeyContent,
) -> Result<SasState<KeyReceived>, SasState<Cancelled>> {
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<Started> {
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<Accepted> {
sender: &UserId,
content: &KeyContent,
) -> Result<SasState<KeyReceived>, SasState<Cancelled>> {
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<Accepted> {
);
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<Accepted> {
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<Accepted> {
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<KeyReceived> {
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<KeyReceived> {
sender: &UserId,
content: &MacContent,
) -> Result<SasState<MacReceived>, SasState<Cancelled>> {
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<KeyReceived> {
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<Confirmed> {
sender: &UserId,
content: &MacContent,
) -> Result<SasState<Done>, SasState<Cancelled>> {
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<Confirmed> {
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<Confirmed> {
sender: &UserId,
content: &MacContent,
) -> Result<SasState<WaitingForDone>, SasState<Cancelled>> {
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<Confirmed> {
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<WaitingForDone> {
sender: &UserId,
content: &DoneContent,
) -> Result<SasState<Done>, SasState<Cancelled>> {
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<Done> {
}
/// 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<Created>, SasState<Started>) {
async fn get_sas_pair() -> (SasState<Created>, SasState<WeAccepted>) {
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::<Started>::from_start_event(
bob.clone(),
alice_device,
None,
flow_id,
flow_id.into(),
&content,
false,
)

View File

@ -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"

View File

@ -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};