Merge branch 'crypto-improvements' into master
This commit is contained in:
commit
176181bdcf
29 changed files with 1508 additions and 581 deletions
|
@ -24,7 +24,7 @@ sqlite_cryptostore = ["matrix-sdk-base/sqlite_cryptostore"]
|
|||
docs = ["encryption", "sqlite_cryptostore", "messages"]
|
||||
|
||||
[dependencies]
|
||||
async-trait = "0.1.37"
|
||||
async-trait = "0.1.38"
|
||||
dashmap = { version = "3.11.10", optional = true }
|
||||
http = "0.2.1"
|
||||
# FIXME: Revert to regular dependency once 0.10.8 or 0.11.0 is released
|
||||
|
@ -55,7 +55,7 @@ version = "3.0.2"
|
|||
features = ["wasm-bindgen"]
|
||||
|
||||
[dev-dependencies]
|
||||
async-trait = "0.1.37"
|
||||
async-trait = "0.1.38"
|
||||
dirs = "3.0.1"
|
||||
matrix-sdk-test = { version = "0.1.0", path = "../matrix_sdk_test" }
|
||||
tokio = { version = "0.2.22", features = ["rt-threaded", "macros"] }
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
use std::{env, io, process::exit};
|
||||
use url::Url;
|
||||
|
||||
use matrix_sdk::{self, events::AnyToDeviceEvent, Client, ClientConfig, Sas, SyncSettings};
|
||||
use matrix_sdk::{
|
||||
self, events::AnyToDeviceEvent, identifiers::UserId, Client, ClientConfig, Sas, SyncSettings,
|
||||
};
|
||||
|
||||
async fn wait_for_confirmation(sas: Sas) {
|
||||
println!("Emoji: {:?}", sas.emoji());
|
||||
async fn wait_for_confirmation(client: Client, sas: Sas) {
|
||||
println!("Does the emoji match: {:?}", sas.emoji());
|
||||
|
||||
let mut input = String::new();
|
||||
io::stdin()
|
||||
|
@ -16,14 +18,15 @@ async fn wait_for_confirmation(sas: Sas) {
|
|||
sas.confirm().await.unwrap();
|
||||
|
||||
if sas.is_done() {
|
||||
print_result(sas);
|
||||
print_result(&sas);
|
||||
print_devices(sas.other_device().user_id(), &client).await;
|
||||
}
|
||||
}
|
||||
_ => sas.cancel().await.unwrap(),
|
||||
}
|
||||
}
|
||||
|
||||
fn print_result(sas: Sas) {
|
||||
fn print_result(sas: &Sas) {
|
||||
let device = sas.other_device();
|
||||
|
||||
println!(
|
||||
|
@ -34,12 +37,28 @@ fn print_result(sas: Sas) {
|
|||
);
|
||||
}
|
||||
|
||||
async fn print_devices(user_id: &UserId, client: &Client) {
|
||||
println!("Devices of user {}", user_id);
|
||||
|
||||
for device in client.get_user_devices(user_id).await.unwrap().devices() {
|
||||
println!(
|
||||
" {:<10} {:<30} {:<}",
|
||||
device.device_id(),
|
||||
device.display_name().as_deref().unwrap_or_default(),
|
||||
device.is_trusted()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async fn login(
|
||||
homeserver_url: String,
|
||||
username: &str,
|
||||
password: &str,
|
||||
) -> Result<(), matrix_sdk::Error> {
|
||||
let client_config = ClientConfig::new();
|
||||
let client_config = ClientConfig::new()
|
||||
.disable_ssl_verification()
|
||||
.proxy("http://localhost:8080")
|
||||
.unwrap();
|
||||
let homeserver_url = Url::parse(&homeserver_url).expect("Couldn't parse the homeserver URL");
|
||||
let client = Client::new_with_config(homeserver_url, client_config).unwrap();
|
||||
|
||||
|
@ -64,6 +83,12 @@ async fn login(
|
|||
.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();
|
||||
}
|
||||
|
||||
|
@ -73,7 +98,7 @@ async fn login(
|
|||
.await
|
||||
.expect("Sas object wasn't created");
|
||||
|
||||
tokio::spawn(wait_for_confirmation(sas));
|
||||
tokio::spawn(wait_for_confirmation((*client).clone(), sas));
|
||||
}
|
||||
|
||||
AnyToDeviceEvent::KeyVerificationMac(e) => {
|
||||
|
@ -83,7 +108,8 @@ async fn login(
|
|||
.expect("Sas object wasn't created");
|
||||
|
||||
if sas.is_done() {
|
||||
print_result(sas);
|
||||
print_result(&sas);
|
||||
print_devices(&e.sender, &client).await;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -13,8 +13,6 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#[cfg(feature = "encryption")]
|
||||
use std::collections::BTreeMap;
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
convert::{TryFrom, TryInto},
|
||||
|
@ -38,7 +36,7 @@ use tracing::{error, info, instrument};
|
|||
use matrix_sdk_base::{BaseClient, BaseClientConfig, Room, Session, StateStore};
|
||||
|
||||
#[cfg(feature = "encryption")]
|
||||
use matrix_sdk_base::CryptoStoreError;
|
||||
use matrix_sdk_base::{CryptoStoreError, OutgoingRequests};
|
||||
|
||||
use matrix_sdk_common::{
|
||||
api::r0::{
|
||||
|
@ -76,7 +74,6 @@ use matrix_sdk_common::{
|
|||
Response as ToDeviceResponse,
|
||||
},
|
||||
},
|
||||
identifiers::DeviceKeyAlgorithm,
|
||||
locks::Mutex,
|
||||
};
|
||||
|
||||
|
@ -976,10 +973,9 @@ impl Client {
|
|||
self.base_client.get_missing_sessions(members).await?
|
||||
};
|
||||
|
||||
if !missing_sessions.is_empty() {
|
||||
self.claim_one_time_keys(missing_sessions).await?;
|
||||
if let Some((request_id, request)) = missing_sessions {
|
||||
self.claim_one_time_keys(&request_id, request).await?;
|
||||
}
|
||||
|
||||
let response = self.share_group_session(room_id).await;
|
||||
|
||||
self.group_session_locks.remove(room_id);
|
||||
|
@ -1119,11 +1115,11 @@ impl Client {
|
|||
}
|
||||
|
||||
#[cfg(feature = "encryption")]
|
||||
async fn send_to_device(&self, request: OwnedToDeviceRequest) -> Result<ToDeviceResponse> {
|
||||
async fn send_to_device(&self, request: &OwnedToDeviceRequest) -> Result<ToDeviceResponse> {
|
||||
let request = ToDeviceRequest {
|
||||
event_type: request.event_type,
|
||||
event_type: request.event_type.clone(),
|
||||
txn_id: &request.txn_id,
|
||||
messages: request.messages,
|
||||
messages: request.messages.clone(),
|
||||
};
|
||||
|
||||
self.send(request).await
|
||||
|
@ -1241,29 +1237,27 @@ impl Client {
|
|||
|
||||
#[cfg(feature = "encryption")]
|
||||
{
|
||||
if self.base_client.should_upload_keys().await {
|
||||
let response = self.keys_upload().await;
|
||||
for r in self.base_client.outgoing_requests().await {
|
||||
match r.request() {
|
||||
OutgoingRequests::KeysQuery(request) => {
|
||||
if let Err(e) = self.keys_query(r.request_id(), request).await {
|
||||
warn!("Error while querying device keys {:?}", e);
|
||||
}
|
||||
}
|
||||
|
||||
if let Err(e) = response {
|
||||
warn!("Error while uploading E2EE keys {:?}", e);
|
||||
}
|
||||
}
|
||||
|
||||
if self.base_client.should_query_keys().await {
|
||||
let response = self.keys_query().await;
|
||||
|
||||
if let Err(e) = response {
|
||||
warn!("Error while querying device keys {:?}", e);
|
||||
}
|
||||
}
|
||||
|
||||
for request in self.base_client.outgoing_to_device_requests().await {
|
||||
let txn_id = request.txn_id.clone();
|
||||
|
||||
if self.send_to_device(request).await.is_ok() {
|
||||
self.base_client
|
||||
.mark_to_device_request_as_sent(&txn_id)
|
||||
.await;
|
||||
OutgoingRequests::KeysUpload(request) => {
|
||||
if let Err(e) = self.keys_upload(&r.request_id(), request).await {
|
||||
warn!("Error while querying device keys {:?}", e);
|
||||
}
|
||||
}
|
||||
OutgoingRequests::ToDeviceRequest(request) => {
|
||||
if let Ok(resp) = self.send_to_device(request).await {
|
||||
self.base_client
|
||||
.mark_request_as_sent(&r.request_id(), &resp)
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1309,16 +1303,12 @@ impl Client {
|
|||
#[instrument]
|
||||
async fn claim_one_time_keys(
|
||||
&self,
|
||||
one_time_keys: BTreeMap<UserId, BTreeMap<Box<DeviceId>, DeviceKeyAlgorithm>>,
|
||||
request_id: &Uuid,
|
||||
request: claim_keys::Request,
|
||||
) -> Result<claim_keys::Response> {
|
||||
let request = claim_keys::Request {
|
||||
timeout: None,
|
||||
one_time_keys,
|
||||
};
|
||||
|
||||
let response = self.send(request).await?;
|
||||
self.base_client
|
||||
.receive_keys_claim_response(&response)
|
||||
.mark_request_as_sent(request_id, &response)
|
||||
.await?;
|
||||
Ok(response)
|
||||
}
|
||||
|
@ -1344,7 +1334,7 @@ impl Client {
|
|||
.expect("Keys don't need to be uploaded");
|
||||
|
||||
for request in requests.drain(..) {
|
||||
self.send_to_device(request).await?;
|
||||
self.send_to_device(&request).await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
@ -1362,23 +1352,22 @@ impl Client {
|
|||
#[cfg(feature = "encryption")]
|
||||
#[cfg_attr(feature = "docs", doc(cfg(encryption)))]
|
||||
#[instrument]
|
||||
async fn keys_upload(&self) -> Result<upload_keys::Response> {
|
||||
let request = self
|
||||
.base_client
|
||||
.keys_for_upload()
|
||||
.await
|
||||
.expect("Keys don't need to be uploaded");
|
||||
|
||||
async fn keys_upload(
|
||||
&self,
|
||||
request_id: &Uuid,
|
||||
request: &upload_keys::Request,
|
||||
) -> Result<upload_keys::Response> {
|
||||
debug!(
|
||||
"Uploading encryption keys device keys: {}, one-time-keys: {}",
|
||||
request.device_keys.is_some(),
|
||||
request.one_time_keys.as_ref().map_or(0, |k| k.len())
|
||||
);
|
||||
|
||||
let response = self.send(request).await?;
|
||||
let response = self.send(request.clone()).await?;
|
||||
self.base_client
|
||||
.receive_keys_upload_response(&response)
|
||||
.mark_request_as_sent(request_id, &response)
|
||||
.await?;
|
||||
|
||||
Ok(response)
|
||||
}
|
||||
|
||||
|
@ -1396,33 +1385,20 @@ impl Client {
|
|||
#[cfg(feature = "encryption")]
|
||||
#[cfg_attr(feature = "docs", doc(cfg(encryption)))]
|
||||
#[instrument]
|
||||
async fn keys_query(&self) -> Result<get_keys::Response> {
|
||||
let mut users_for_query = self
|
||||
.base_client
|
||||
.users_for_key_query()
|
||||
.await
|
||||
.expect("Keys don't need to be uploaded");
|
||||
|
||||
debug!(
|
||||
"Querying device keys device for users: {:?}",
|
||||
users_for_query
|
||||
);
|
||||
|
||||
let mut device_keys: BTreeMap<UserId, Vec<Box<DeviceId>>> = BTreeMap::new();
|
||||
|
||||
for user in users_for_query.drain() {
|
||||
device_keys.insert(user, Vec::new());
|
||||
}
|
||||
|
||||
async fn keys_query(
|
||||
&self,
|
||||
request_id: &Uuid,
|
||||
request: &get_keys::IncomingRequest,
|
||||
) -> Result<get_keys::Response> {
|
||||
let request = get_keys::Request {
|
||||
timeout: None,
|
||||
device_keys,
|
||||
device_keys: request.device_keys.clone(),
|
||||
token: None,
|
||||
};
|
||||
|
||||
let response = self.send(request).await?;
|
||||
self.base_client
|
||||
.receive_keys_query_response(&response)
|
||||
.mark_request_as_sent(request_id, &response)
|
||||
.await?;
|
||||
|
||||
Ok(response)
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
use std::{ops::Deref, result::Result as StdResult};
|
||||
|
||||
use matrix_sdk_base::{
|
||||
CryptoStoreError, Device as BaseDevice, ReadOnlyDevice, TrustState,
|
||||
CryptoStoreError, Device as BaseDevice, LocalTrust, ReadOnlyDevice,
|
||||
UserDevices as BaseUserDevices,
|
||||
};
|
||||
use matrix_sdk_common::{
|
||||
|
@ -61,7 +61,7 @@ impl Device {
|
|||
/// # });
|
||||
/// ```
|
||||
pub async fn start_verification(&self) -> Result<Sas> {
|
||||
let (sas, request) = self.inner.start_verification();
|
||||
let (sas, request) = self.inner.start_verification().await?;
|
||||
let request = ToDeviceRequest {
|
||||
event_type: request.event_type,
|
||||
txn_id: &request.txn_id,
|
||||
|
@ -76,16 +76,24 @@ impl Device {
|
|||
})
|
||||
}
|
||||
|
||||
/// Set the trust state of the device to the given state.
|
||||
/// Is the device trusted.
|
||||
pub fn is_trusted(&self) -> bool {
|
||||
self.inner.trust_state()
|
||||
}
|
||||
|
||||
/// Set the local trust state of the device to the given state.
|
||||
///
|
||||
/// This won't affect any cross signing trust state, this only sets a flag
|
||||
/// marking to have the given trust state.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `trust_state` - The new trust state that should be set for the device.
|
||||
pub async fn set_trust_state(
|
||||
pub async fn set_local_trust(
|
||||
&self,
|
||||
trust_state: TrustState,
|
||||
trust_state: LocalTrust,
|
||||
) -> StdResult<(), CryptoStoreError> {
|
||||
self.inner.set_trust_state(trust_state).await
|
||||
self.inner.set_local_trust(trust_state).await
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -41,7 +41,7 @@
|
|||
pub use matrix_sdk_base::JsonStore;
|
||||
#[cfg(feature = "encryption")]
|
||||
#[cfg_attr(feature = "docs", doc(cfg(encryption)))]
|
||||
pub use matrix_sdk_base::TrustState;
|
||||
pub use matrix_sdk_base::LocalTrust;
|
||||
pub use matrix_sdk_base::{
|
||||
CustomEvent, Error as BaseError, EventEmitter, Room, RoomState, Session, StateStore, SyncRoom,
|
||||
};
|
||||
|
|
|
@ -56,7 +56,7 @@ impl Sas {
|
|||
|
||||
/// Cancel the interactive verification flow.
|
||||
pub async fn cancel(&self) -> Result<()> {
|
||||
if let Some(req) = self.inner.cancel() {
|
||||
if let Some((_, req)) = self.inner.cancel() {
|
||||
let request = ToDeviceRequest {
|
||||
event_type: req.event_type,
|
||||
txn_id: &req.txn_id,
|
||||
|
|
|
@ -23,7 +23,7 @@ sqlite_cryptostore = ["matrix-sdk-crypto/sqlite_cryptostore"]
|
|||
docs = ["encryption", "sqlite_cryptostore", "messages"]
|
||||
|
||||
[dependencies]
|
||||
async-trait = "0.1.37"
|
||||
async-trait = "0.1.38"
|
||||
serde = "1.0.115"
|
||||
serde_json = "1.0.57"
|
||||
zeroize = "1.1.0"
|
||||
|
|
|
@ -13,8 +13,6 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#[cfg(feature = "encryption")]
|
||||
use std::collections::{BTreeMap, HashSet};
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
fmt,
|
||||
|
@ -36,24 +34,22 @@ use matrix_sdk_common::{
|
|||
identifiers::{RoomId, UserId},
|
||||
locks::RwLock,
|
||||
push::Ruleset,
|
||||
uuid::Uuid,
|
||||
Raw,
|
||||
};
|
||||
#[cfg(feature = "encryption")]
|
||||
use matrix_sdk_common::{
|
||||
api::r0::keys::{
|
||||
claim_keys::Response as KeysClaimResponse,
|
||||
get_keys::Response as KeysQueryResponse,
|
||||
upload_keys::{Request as KeysUploadRequest, Response as KeysUploadResponse},
|
||||
},
|
||||
api::r0::keys::claim_keys::Request as KeysClaimRequest,
|
||||
api::r0::to_device::send_event_to_device::IncomingRequest as OwnedToDeviceRequest,
|
||||
events::room::{
|
||||
encrypted::EncryptedEventContent, message::MessageEventContent as MsgEventContent,
|
||||
},
|
||||
identifiers::{DeviceId, DeviceKeyAlgorithm},
|
||||
identifiers::DeviceId,
|
||||
};
|
||||
#[cfg(feature = "encryption")]
|
||||
use matrix_sdk_crypto::{
|
||||
CryptoStore, CryptoStoreError, Device, OlmError, OlmMachine, Sas, UserDevices,
|
||||
CryptoStore, CryptoStoreError, Device, IncomingResponse, OlmError, OlmMachine, OutgoingRequest,
|
||||
Sas, UserDevices,
|
||||
};
|
||||
use zeroize::Zeroizing;
|
||||
|
||||
|
@ -1229,18 +1225,6 @@ impl BaseClient {
|
|||
Ok(updated)
|
||||
}
|
||||
|
||||
/// Should account or one-time keys be uploaded to the server.
|
||||
#[cfg(feature = "encryption")]
|
||||
#[cfg_attr(feature = "docs", doc(cfg(encryption)))]
|
||||
pub async fn should_upload_keys(&self) -> bool {
|
||||
let olm = self.olm.lock().await;
|
||||
|
||||
match &*olm {
|
||||
Some(o) => o.should_upload_keys().await,
|
||||
None => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Should the client share a group session for the given room.
|
||||
///
|
||||
/// Returns true if a session needs to be shared before room messages can be
|
||||
|
@ -1260,15 +1244,31 @@ impl BaseClient {
|
|||
}
|
||||
}
|
||||
|
||||
/// Should users be queried for their device keys.
|
||||
/// Get the list of outgoing requests that need to be sent out.
|
||||
#[cfg(feature = "encryption")]
|
||||
#[cfg_attr(feature = "docs", doc(cfg(encryption)))]
|
||||
pub async fn should_query_keys(&self) -> bool {
|
||||
pub async fn outgoing_requests(&self) -> Vec<OutgoingRequest> {
|
||||
let olm = self.olm.lock().await;
|
||||
|
||||
match &*olm {
|
||||
Some(o) => o.should_query_keys().await,
|
||||
None => false,
|
||||
Some(o) => o.outgoing_requests().await,
|
||||
None => vec![],
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the list of outgoing requests that need to be sent out.
|
||||
#[cfg(feature = "encryption")]
|
||||
#[cfg_attr(feature = "docs", doc(cfg(encryption)))]
|
||||
pub async fn mark_request_as_sent<'a>(
|
||||
&self,
|
||||
request_id: &Uuid,
|
||||
response: impl Into<IncomingResponse<'a>>,
|
||||
) -> Result<()> {
|
||||
let olm = self.olm.lock().await;
|
||||
|
||||
match &*olm {
|
||||
Some(o) => Ok(o.mark_requests_as_sent(request_id, response).await?),
|
||||
None => Ok(()),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1280,12 +1280,12 @@ impl BaseClient {
|
|||
pub async fn get_missing_sessions(
|
||||
&self,
|
||||
users: impl Iterator<Item = &UserId>,
|
||||
) -> Result<BTreeMap<UserId, BTreeMap<Box<DeviceId>, DeviceKeyAlgorithm>>> {
|
||||
) -> Result<Option<(Uuid, KeysClaimRequest)>> {
|
||||
let olm = self.olm.lock().await;
|
||||
|
||||
match &*olm {
|
||||
Some(o) => Ok(o.get_missing_sessions(users).await?),
|
||||
None => Ok(BTreeMap::new()),
|
||||
None => Ok(None),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1332,92 +1332,6 @@ impl BaseClient {
|
|||
}
|
||||
}
|
||||
|
||||
/// Get a tuple of device and one-time keys that need to be uploaded.
|
||||
///
|
||||
/// Returns an empty error if no keys need to be uploaded.
|
||||
#[cfg(feature = "encryption")]
|
||||
#[cfg_attr(feature = "docs", doc(cfg(encryption)))]
|
||||
pub async fn keys_for_upload(&self) -> StdResult<KeysUploadRequest, ()> {
|
||||
let olm = self.olm.lock().await;
|
||||
|
||||
match &*olm {
|
||||
Some(o) => o.keys_for_upload().await,
|
||||
None => Err(()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the users that we need to query keys for.
|
||||
///
|
||||
/// Returns an empty error if no keys need to be queried.
|
||||
#[cfg(feature = "encryption")]
|
||||
#[cfg_attr(feature = "docs", doc(cfg(encryption)))]
|
||||
pub async fn users_for_key_query(&self) -> StdResult<HashSet<UserId>, ()> {
|
||||
let olm = self.olm.lock().await;
|
||||
|
||||
match &*olm {
|
||||
Some(o) => Ok(o.users_for_key_query().await),
|
||||
None => Err(()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Receive a successful keys upload response.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `response` - The keys upload response of the request that the client
|
||||
/// performed.
|
||||
///
|
||||
/// # Panics
|
||||
/// Panics if the client hasn't been logged in.
|
||||
#[cfg(feature = "encryption")]
|
||||
#[cfg_attr(feature = "docs", doc(cfg(encryption)))]
|
||||
pub async fn receive_keys_upload_response(&self, response: &KeysUploadResponse) -> Result<()> {
|
||||
let olm = self.olm.lock().await;
|
||||
|
||||
let o = olm.as_ref().expect("Client isn't logged in.");
|
||||
o.receive_keys_upload_response(response).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Receive a successful keys claim response.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `response` - The keys claim response of the request that the client
|
||||
/// performed.
|
||||
///
|
||||
/// # Panics
|
||||
/// Panics if the client hasn't been logged in.
|
||||
#[cfg(feature = "encryption")]
|
||||
#[cfg_attr(feature = "docs", doc(cfg(encryption)))]
|
||||
pub async fn receive_keys_claim_response(&self, response: &KeysClaimResponse) -> Result<()> {
|
||||
let olm = self.olm.lock().await;
|
||||
|
||||
let o = olm.as_ref().expect("Client isn't logged in.");
|
||||
o.receive_keys_claim_response(response).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Receive a successful keys query response.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `response` - The keys query response of the request that the client
|
||||
/// performed.
|
||||
///
|
||||
/// # Panics
|
||||
/// Panics if the client hasn't been logged in.
|
||||
#[cfg(feature = "encryption")]
|
||||
#[cfg_attr(feature = "docs", doc(cfg(encryption)))]
|
||||
pub async fn receive_keys_query_response(&self, response: &KeysQueryResponse) -> Result<()> {
|
||||
let olm = self.olm.lock().await;
|
||||
|
||||
let o = olm.as_ref().expect("Client isn't logged in.");
|
||||
o.receive_keys_query_response(response).await?;
|
||||
// TODO notify our callers of new devices via some callback.
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Invalidate the currently active outbound group session for the given
|
||||
/// room.
|
||||
///
|
||||
|
@ -1834,27 +1748,6 @@ impl BaseClient {
|
|||
}
|
||||
}
|
||||
|
||||
/// Get the to-device requests that need to be sent out.
|
||||
#[cfg(feature = "encryption")]
|
||||
#[cfg_attr(feature = "docs", doc(cfg(encryption)))]
|
||||
pub async fn outgoing_to_device_requests(&self) -> Vec<OwnedToDeviceRequest> {
|
||||
self.olm
|
||||
.lock()
|
||||
.await
|
||||
.as_ref()
|
||||
.map(|o| o.outgoing_to_device_requests())
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
/// Mark an outgoing to-device requests as sent.
|
||||
#[cfg(feature = "encryption")]
|
||||
#[cfg_attr(feature = "docs", doc(cfg(encryption)))]
|
||||
pub async fn mark_to_device_request_as_sent(&self, request_id: &str) {
|
||||
if let Some(olm) = self.olm.lock().await.as_ref() {
|
||||
olm.mark_to_device_request_as_sent(request_id)
|
||||
}
|
||||
}
|
||||
|
||||
/// Get a `Sas` verification object with the given flow id.
|
||||
///
|
||||
/// # Arguments
|
||||
|
|
|
@ -57,7 +57,8 @@ pub use state::{AllRooms, ClientState};
|
|||
#[cfg(feature = "encryption")]
|
||||
#[cfg_attr(feature = "docs", doc(cfg(encryption)))]
|
||||
pub use matrix_sdk_crypto::{
|
||||
CryptoStoreError, Device, ReadOnlyDevice, Sas, TrustState, UserDevices,
|
||||
CryptoStoreError, Device, IncomingResponse, LocalTrust, OutgoingRequest, OutgoingRequests,
|
||||
ReadOnlyDevice, Sas, UserDevices,
|
||||
};
|
||||
|
||||
#[cfg(feature = "messages")]
|
||||
|
|
|
@ -165,7 +165,7 @@ impl Into<EncryptionSettings> for EncryptionInfo {
|
|||
fn into(self) -> EncryptionSettings {
|
||||
EncryptionSettings {
|
||||
algorithm: self.algorithm,
|
||||
rotation_period: Duration::from_millis(self.rotation_period_messages),
|
||||
rotation_period: Duration::from_millis(self.rotation_period_ms),
|
||||
rotation_period_msgs: self.rotation_period_messages,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,7 +20,7 @@ sqlite_cryptostore = ["sqlx"]
|
|||
docs = ["sqlite_cryptostore"]
|
||||
|
||||
[dependencies]
|
||||
async-trait = "0.1.37"
|
||||
async-trait = "0.1.38"
|
||||
|
||||
matrix-sdk-common-macros = { version = "0.1.0", path = "../matrix_sdk_common_macros" }
|
||||
matrix-sdk-common = { version = "0.1.0", path = "../matrix_sdk_common" }
|
||||
|
@ -52,7 +52,7 @@ features = ["runtime-tokio", "sqlite"]
|
|||
[dev-dependencies]
|
||||
tokio = { version = "0.2.22", features = ["rt-threaded", "macros"] }
|
||||
futures = "0.3.5"
|
||||
proptest = "0.10.0"
|
||||
proptest = "0.10.1"
|
||||
serde_json = "1.0.57"
|
||||
tempfile = "3.1.0"
|
||||
http = "0.2.1"
|
||||
|
|
|
@ -28,15 +28,20 @@ use matrix_sdk_common::{
|
|||
keys::SignedKey, to_device::send_event_to_device::IncomingRequest as OwnedToDeviceRequest,
|
||||
},
|
||||
encryption::DeviceKeys,
|
||||
events::{room::encrypted::EncryptedEventContent, EventType},
|
||||
identifiers::{DeviceId, DeviceKeyAlgorithm, DeviceKeyId, EventEncryptionAlgorithm, UserId},
|
||||
};
|
||||
use serde_json::{json, Value};
|
||||
use tracing::warn;
|
||||
|
||||
#[cfg(test)]
|
||||
use super::{Account, OlmMachine};
|
||||
|
||||
use crate::{
|
||||
error::SignatureError, store::Result as StoreResult, verification::VerificationMachine,
|
||||
error::{EventError, OlmError, OlmResult, SignatureError},
|
||||
store::Result as StoreResult,
|
||||
user_identity::{OwnUserIdentity, UserIdentities},
|
||||
verification::VerificationMachine,
|
||||
verify_json, ReadOnlyUserDevices, Sas,
|
||||
};
|
||||
|
||||
|
@ -50,7 +55,7 @@ pub struct ReadOnlyDevice {
|
|||
signatures: Arc<BTreeMap<UserId, BTreeMap<DeviceKeyId, String>>>,
|
||||
display_name: Arc<Option<String>>,
|
||||
deleted: Arc<AtomicBool>,
|
||||
trust_state: Arc<Atomic<TrustState>>,
|
||||
trust_state: Arc<Atomic<LocalTrust>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
|
@ -58,6 +63,8 @@ pub struct ReadOnlyDevice {
|
|||
pub struct Device {
|
||||
pub(crate) inner: ReadOnlyDevice,
|
||||
pub(crate) verification_machine: VerificationMachine,
|
||||
pub(crate) own_identity: Option<OwnUserIdentity>,
|
||||
pub(crate) device_owner_identity: Option<UserIdentities>,
|
||||
}
|
||||
|
||||
impl Deref for Device {
|
||||
|
@ -72,22 +79,119 @@ impl Device {
|
|||
/// Start a interactive verification with this `Device`
|
||||
///
|
||||
/// Returns a `Sas` object and to-device request that needs to be sent out.
|
||||
pub fn start_verification(&self) -> (Sas, OwnedToDeviceRequest) {
|
||||
self.verification_machine.start_sas(self.inner.clone())
|
||||
pub async fn start_verification(&self) -> StoreResult<(Sas, OwnedToDeviceRequest)> {
|
||||
self.verification_machine
|
||||
.start_sas(self.inner.clone())
|
||||
.await
|
||||
}
|
||||
|
||||
/// Set the trust state of the device to the given state.
|
||||
/// Get the trust state of the device.
|
||||
pub fn trust_state(&self) -> 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.inner.trust_state() == LocalTrust::Verified {
|
||||
// If the device is localy marked as verified just return so, no
|
||||
// need to check signatures.
|
||||
true
|
||||
} else {
|
||||
self.own_identity.as_ref().map_or(false, |own_identity| {
|
||||
// Our own identity needs to be marked as verified.
|
||||
own_identity.is_verified()
|
||||
&& self
|
||||
.device_owner_identity
|
||||
.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.inner)
|
||||
.map_or(false, |_| true),
|
||||
|
||||
// If it's a device from someone else, first check
|
||||
// that our user has signed the other user and then
|
||||
// checkif 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.inner)
|
||||
.map_or(false, |_| true)
|
||||
}
|
||||
})
|
||||
.unwrap_or(false)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the local trust state of the device to the given state.
|
||||
///
|
||||
/// This won't affect any cross signing trust state, this only sets a flag
|
||||
/// marking to have the given trust state.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `trust_state` - The new trust state that should be set for the device.
|
||||
pub async fn set_trust_state(&self, trust_state: TrustState) -> StoreResult<()> {
|
||||
pub async fn set_local_trust(&self, trust_state: LocalTrust) -> StoreResult<()> {
|
||||
self.inner.set_trust_state(trust_state);
|
||||
|
||||
self.verification_machine
|
||||
.store
|
||||
.save_devices(&[self.inner.clone()])
|
||||
.await
|
||||
}
|
||||
|
||||
/// Encrypt the given content for this `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,
|
||||
) -> OlmResult<EncryptedEventContent> {
|
||||
let sender_key = if let Some(k) = self.inner.get_key(DeviceKeyAlgorithm::Curve25519) {
|
||||
k
|
||||
} else {
|
||||
warn!(
|
||||
"Trying to encrypt a Megolm session for user {} on device {}, \
|
||||
but the device doesn't have a curve25519 key",
|
||||
self.user_id(),
|
||||
self.device_id()
|
||||
);
|
||||
return Err(EventError::MissingSenderKey.into());
|
||||
};
|
||||
|
||||
let mut session = if let Some(s) = self
|
||||
.verification_machine
|
||||
.store
|
||||
.get_sessions(sender_key)
|
||||
.await?
|
||||
{
|
||||
let session = &s.lock().await[0];
|
||||
session.clone()
|
||||
} else {
|
||||
warn!(
|
||||
"Trying to encrypt a Megolm session for user {} on device {}, \
|
||||
but no Olm session is found",
|
||||
self.user_id(),
|
||||
self.device_id()
|
||||
);
|
||||
return Err(OlmError::MissingSession);
|
||||
};
|
||||
|
||||
let message = session.encrypt(&self.inner, event_type, content).await;
|
||||
self.verification_machine
|
||||
.store
|
||||
.save_sessions(&[session])
|
||||
.await?;
|
||||
|
||||
message
|
||||
}
|
||||
}
|
||||
|
||||
/// A read only view over all devices belonging to a user.
|
||||
|
@ -95,6 +199,8 @@ impl Device {
|
|||
pub struct UserDevices {
|
||||
pub(crate) inner: ReadOnlyUserDevices,
|
||||
pub(crate) verification_machine: VerificationMachine,
|
||||
pub(crate) own_identity: Option<OwnUserIdentity>,
|
||||
pub(crate) device_owner_identity: Option<UserIdentities>,
|
||||
}
|
||||
|
||||
impl UserDevices {
|
||||
|
@ -103,6 +209,8 @@ impl UserDevices {
|
|||
self.inner.get(device_id).map(|d| Device {
|
||||
inner: d,
|
||||
verification_machine: self.verification_machine.clone(),
|
||||
own_identity: self.own_identity.clone(),
|
||||
device_owner_identity: self.device_owner_identity.clone(),
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -113,18 +221,18 @@ impl UserDevices {
|
|||
|
||||
/// Iterator over all the devices of the user devices.
|
||||
pub fn devices(&self) -> impl Iterator<Item = Device> + '_ {
|
||||
let machine = self.verification_machine.clone();
|
||||
|
||||
self.inner.devices().map(move |d| Device {
|
||||
inner: d.clone(),
|
||||
verification_machine: machine.clone(),
|
||||
verification_machine: self.verification_machine.clone(),
|
||||
own_identity: self.own_identity.clone(),
|
||||
device_owner_identity: self.device_owner_identity.clone(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
/// The trust state of a device.
|
||||
pub enum TrustState {
|
||||
/// The local trust state of a device.
|
||||
pub enum LocalTrust {
|
||||
/// The device has been verified and is trusted.
|
||||
Verified = 0,
|
||||
/// The device been blacklisted from communicating.
|
||||
|
@ -135,14 +243,14 @@ pub enum TrustState {
|
|||
Unset = 3,
|
||||
}
|
||||
|
||||
impl From<i64> for TrustState {
|
||||
impl From<i64> for LocalTrust {
|
||||
fn from(state: i64) -> Self {
|
||||
match state {
|
||||
0 => TrustState::Verified,
|
||||
1 => TrustState::BlackListed,
|
||||
2 => TrustState::Ignored,
|
||||
3 => TrustState::Unset,
|
||||
_ => TrustState::Unset,
|
||||
0 => LocalTrust::Verified,
|
||||
1 => LocalTrust::BlackListed,
|
||||
2 => LocalTrust::Ignored,
|
||||
3 => LocalTrust::Unset,
|
||||
_ => LocalTrust::Unset,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -153,7 +261,7 @@ impl ReadOnlyDevice {
|
|||
user_id: UserId,
|
||||
device_id: Box<DeviceId>,
|
||||
display_name: Option<String>,
|
||||
trust_state: TrustState,
|
||||
trust_state: LocalTrust,
|
||||
algorithms: Vec<EventEncryptionAlgorithm>,
|
||||
keys: BTreeMap<DeviceKeyId, String>,
|
||||
signatures: BTreeMap<UserId, BTreeMap<DeviceKeyId, String>>,
|
||||
|
@ -202,27 +310,27 @@ impl ReadOnlyDevice {
|
|||
}
|
||||
|
||||
/// Get the trust state of the device.
|
||||
pub fn trust_state(&self) -> TrustState {
|
||||
pub fn trust_state(&self) -> LocalTrust {
|
||||
self.trust_state.load(Ordering::Relaxed)
|
||||
}
|
||||
|
||||
/// Is the device locally marked as trusted.
|
||||
pub fn is_trusted(&self) -> bool {
|
||||
self.trust_state() == TrustState::Verified
|
||||
self.trust_state() == LocalTrust::Verified
|
||||
}
|
||||
|
||||
/// Is the device locally marked as blacklisted.
|
||||
///
|
||||
/// Blacklisted devices won't receive any group sessions.
|
||||
pub fn is_blacklisted(&self) -> bool {
|
||||
self.trust_state() == TrustState::BlackListed
|
||||
self.trust_state() == LocalTrust::BlackListed
|
||||
}
|
||||
|
||||
/// Set the trust state of the device to the given state.
|
||||
///
|
||||
/// Note: This should only done in the cryptostore where the trust state can
|
||||
/// be stored.
|
||||
pub(crate) fn set_trust_state(&self, state: TrustState) {
|
||||
pub(crate) fn set_trust_state(&self, state: LocalTrust) {
|
||||
self.trust_state.store(state, Ordering::Relaxed)
|
||||
}
|
||||
|
||||
|
@ -328,7 +436,7 @@ impl TryFrom<&DeviceKeys> for ReadOnlyDevice {
|
|||
.flatten(),
|
||||
),
|
||||
deleted: Arc::new(AtomicBool::new(false)),
|
||||
trust_state: Arc::new(Atomic::new(TrustState::Unset)),
|
||||
trust_state: Arc::new(Atomic::new(LocalTrust::Unset)),
|
||||
};
|
||||
|
||||
device.verify_device_keys(device_keys)?;
|
||||
|
@ -347,7 +455,7 @@ pub(crate) mod test {
|
|||
use serde_json::json;
|
||||
use std::convert::TryFrom;
|
||||
|
||||
use crate::device::{ReadOnlyDevice, TrustState};
|
||||
use crate::device::{LocalTrust, ReadOnlyDevice};
|
||||
use matrix_sdk_common::{
|
||||
encryption::DeviceKeys,
|
||||
identifiers::{user_id, DeviceKeyAlgorithm},
|
||||
|
@ -393,7 +501,7 @@ pub(crate) mod test {
|
|||
assert_eq!(&user_id, device.user_id());
|
||||
assert_eq!(device_id, device.device_id());
|
||||
assert_eq!(device.algorithms.len(), 2);
|
||||
assert_eq!(TrustState::Unset, device.trust_state());
|
||||
assert_eq!(LocalTrust::Unset, device.trust_state());
|
||||
assert_eq!(
|
||||
"Alice's mobile phone",
|
||||
device.display_name().as_ref().unwrap()
|
||||
|
|
|
@ -126,6 +126,9 @@ pub enum SignatureError {
|
|||
#[error("the signing key is missing from the object that signed the message")]
|
||||
MissingSigningKey,
|
||||
|
||||
#[error("the user id of the signing differs from the subkey user id")]
|
||||
UserIdMissmatch,
|
||||
|
||||
#[error("the provided JSON value isn't an object")]
|
||||
NotAnObject,
|
||||
|
||||
|
|
|
@ -30,20 +30,21 @@
|
|||
mod device;
|
||||
mod error;
|
||||
mod machine;
|
||||
mod memory_stores;
|
||||
mod olm;
|
||||
pub mod memory_stores;
|
||||
pub mod olm;
|
||||
mod requests;
|
||||
mod store;
|
||||
#[allow(dead_code)]
|
||||
mod user_identity;
|
||||
mod verification;
|
||||
|
||||
pub use device::{Device, ReadOnlyDevice, TrustState, UserDevices};
|
||||
pub use device::{Device, LocalTrust, ReadOnlyDevice, UserDevices};
|
||||
pub use error::{MegolmError, OlmError};
|
||||
pub use machine::{OlmMachine, OneTimeKeys};
|
||||
pub use memory_stores::{DeviceStore, GroupSessionStore, ReadOnlyUserDevices, SessionStore};
|
||||
pub use olm::{
|
||||
Account, EncryptionSettings, IdentityKeys, InboundGroupSession, OutboundGroupSession, Session,
|
||||
};
|
||||
pub use machine::OlmMachine;
|
||||
use memory_stores::ReadOnlyUserDevices;
|
||||
pub use olm::EncryptionSettings;
|
||||
pub(crate) use olm::{Account, IdentityKeys, InboundGroupSession, Session};
|
||||
pub use requests::{IncomingResponse, OutgoingRequest, OutgoingRequests};
|
||||
#[cfg(feature = "sqlite_cryptostore")]
|
||||
pub use store::sqlite::SqliteStore;
|
||||
pub use store::{CryptoStore, CryptoStoreError};
|
||||
|
|
|
@ -18,23 +18,26 @@ use std::{
|
|||
collections::{BTreeMap, HashSet},
|
||||
convert::{TryFrom, TryInto},
|
||||
mem,
|
||||
result::Result as StdResult,
|
||||
sync::Arc,
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use dashmap::DashMap;
|
||||
use serde_json::Value;
|
||||
use tracing::{debug, error, info, instrument, trace, warn};
|
||||
|
||||
use api::r0::{
|
||||
keys::{claim_keys, get_keys, upload_keys, OneTimeKey},
|
||||
sync::sync_events::Response as SyncResponse,
|
||||
to_device::{
|
||||
send_event_to_device::IncomingRequest as OwnedToDeviceRequest, DeviceIdOrAllDevices,
|
||||
},
|
||||
};
|
||||
use matrix_sdk_common::{
|
||||
api,
|
||||
api::r0::{
|
||||
keys::{
|
||||
claim_keys::{Request as KeysClaimRequest, Response as KeysClaimResponse},
|
||||
get_keys::{IncomingRequest as KeysQueryRequest, Response as KeysQueryResponse},
|
||||
upload_keys,
|
||||
},
|
||||
sync::sync_events::Response as SyncResponse,
|
||||
to_device::{
|
||||
send_event_to_device::IncomingRequest as OwnedToDeviceRequest, DeviceIdOrAllDevices,
|
||||
},
|
||||
},
|
||||
encryption::DeviceKeys,
|
||||
events::{
|
||||
forwarded_room_key::ForwardedRoomKeyEventContent,
|
||||
|
@ -43,9 +46,7 @@ use matrix_sdk_common::{
|
|||
room_key_request::RoomKeyRequestEventContent,
|
||||
AnySyncRoomEvent, AnyToDeviceEvent, EventType, SyncMessageEvent, ToDeviceEvent,
|
||||
},
|
||||
identifiers::{
|
||||
DeviceId, DeviceKeyAlgorithm, DeviceKeyId, EventEncryptionAlgorithm, RoomId, UserId,
|
||||
},
|
||||
identifiers::{DeviceId, DeviceKeyAlgorithm, EventEncryptionAlgorithm, RoomId, UserId},
|
||||
uuid::Uuid,
|
||||
Raw,
|
||||
};
|
||||
|
@ -59,16 +60,16 @@ use super::{
|
|||
Account, EncryptionSettings, GroupSessionKey, IdentityKeys, InboundGroupSession,
|
||||
OlmMessage, OutboundGroupSession,
|
||||
},
|
||||
requests::{IncomingResponse, OutgoingRequest},
|
||||
store::{memorystore::MemoryStore, Result as StoreResult},
|
||||
user_identity::{
|
||||
MasterPubkey, OwnUserIdentity, SelfSigningPubkey, UserIdentities, UserIdentity,
|
||||
UserSigningPubkey,
|
||||
},
|
||||
verification::{Sas, VerificationMachine},
|
||||
CryptoStore,
|
||||
};
|
||||
|
||||
/// A map from the algorithm and device id to a one-time key.
|
||||
///
|
||||
/// These keys need to be periodically uploaded to the server.
|
||||
pub type OneTimeKeys = BTreeMap<DeviceKeyId, OneTimeKey>;
|
||||
|
||||
/// State machine implementation of the Olm/Megolm encryption protocol used for
|
||||
/// Matrix end to end encryption.
|
||||
#[derive(Clone)]
|
||||
|
@ -102,6 +103,7 @@ impl std::fmt::Debug for OlmMachine {
|
|||
|
||||
impl OlmMachine {
|
||||
const MAX_TO_DEVICE_MESSAGES: usize = 20;
|
||||
const KEY_CLAIM_TIMEOUT: Duration = Duration::from_secs(10);
|
||||
|
||||
/// Create a new memory based OlmMachine.
|
||||
///
|
||||
|
@ -188,10 +190,10 @@ impl OlmMachine {
|
|||
#[cfg(feature = "sqlite_cryptostore")]
|
||||
#[instrument(skip(path, passphrase))]
|
||||
#[cfg_attr(feature = "docs", doc(cfg(r#sqlite_cryptostore)))]
|
||||
pub async fn new_with_default_store<P: AsRef<Path>>(
|
||||
pub async fn new_with_default_store(
|
||||
user_id: &UserId,
|
||||
device_id: &DeviceId,
|
||||
path: P,
|
||||
path: impl AsRef<Path>,
|
||||
passphrase: &str,
|
||||
) -> StoreResult<Self> {
|
||||
let store =
|
||||
|
@ -215,6 +217,53 @@ impl OlmMachine {
|
|||
self.account.identity_keys()
|
||||
}
|
||||
|
||||
/// Get the outgoing requests that need to be sent out.
|
||||
pub async fn outgoing_requests(&self) -> Vec<OutgoingRequest> {
|
||||
let mut requests = Vec::new();
|
||||
|
||||
if let Some(r) = self.keys_for_upload().await.map(|r| OutgoingRequest {
|
||||
request_id: Uuid::new_v4(),
|
||||
request: Arc::new(r.into()),
|
||||
}) {
|
||||
requests.push(r);
|
||||
}
|
||||
|
||||
if let Some(r) = self.users_for_key_query().await.map(|r| OutgoingRequest {
|
||||
request_id: Uuid::new_v4(),
|
||||
request: Arc::new(r.into()),
|
||||
}) {
|
||||
requests.push(r);
|
||||
}
|
||||
|
||||
requests.append(&mut self.outgoing_to_device_requests());
|
||||
|
||||
requests
|
||||
}
|
||||
|
||||
/// Mark the request with the given request id as sent.
|
||||
pub async fn mark_requests_as_sent<'a>(
|
||||
&self,
|
||||
request_id: &Uuid,
|
||||
response: impl Into<IncomingResponse<'a>>,
|
||||
) -> OlmResult<()> {
|
||||
match response.into() {
|
||||
IncomingResponse::KeysUpload(response) => {
|
||||
self.receive_keys_upload_response(response).await?;
|
||||
}
|
||||
IncomingResponse::KeysQuery(response) => {
|
||||
self.receive_keys_query_response(response).await?;
|
||||
}
|
||||
IncomingResponse::KeysClaim(response) => {
|
||||
self.receive_keys_claim_response(response).await?;
|
||||
}
|
||||
IncomingResponse::ToDevice(_) => {
|
||||
self.mark_to_device_request_as_sent(&request_id);
|
||||
}
|
||||
};
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Should device or one-time keys be uploaded to the server.
|
||||
///
|
||||
/// This needs to be checked periodically, ideally after every sync request.
|
||||
|
@ -239,7 +288,8 @@ impl OlmMachine {
|
|||
/// }
|
||||
/// # });
|
||||
/// ```
|
||||
pub async fn should_upload_keys(&self) -> bool {
|
||||
#[cfg(test)]
|
||||
async fn should_upload_keys(&self) -> bool {
|
||||
self.account.should_upload_keys().await
|
||||
}
|
||||
|
||||
|
@ -267,7 +317,7 @@ impl OlmMachine {
|
|||
/// * `response` - The keys upload response of the request that the client
|
||||
/// performed.
|
||||
#[instrument]
|
||||
pub async fn receive_keys_upload_response(
|
||||
async fn receive_keys_upload_response(
|
||||
&self,
|
||||
response: &upload_keys::Response,
|
||||
) -> OlmResult<()> {
|
||||
|
@ -294,12 +344,10 @@ impl OlmMachine {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/// Get the user/device pairs for which no Olm session exists.
|
||||
/// Get the a key claiming request for the user/device pairs that we are
|
||||
/// missing Olm sessions for.
|
||||
///
|
||||
/// Returns a map from the user id, to a map from the device id to a key
|
||||
/// algorithm.
|
||||
///
|
||||
/// This can be used to make a key claiming request to the server.
|
||||
/// Returns None if no key claiming request needs to be sent out.
|
||||
///
|
||||
/// Sessions need to be established between devices so group sessions for a
|
||||
/// room can be shared with them.
|
||||
|
@ -307,18 +355,18 @@ impl OlmMachine {
|
|||
/// This should be called every time a group session needs to be shared.
|
||||
///
|
||||
/// The response of a successful key claiming requests needs to be passed to
|
||||
/// the `OlmMachine` with the [`receive_keys_claim_response`].
|
||||
/// the `OlmMachine` with the [`mark_requests_as_sent`].
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// `users` - The list of users that we should check if we lack a session
|
||||
/// with one of their devices.
|
||||
///
|
||||
/// [`receive_keys_claim_response`]: #method.receive_keys_claim_response
|
||||
/// [`mark_requests_as_sent`]: #method.mark_requests_as_sent
|
||||
pub async fn get_missing_sessions(
|
||||
&self,
|
||||
users: impl Iterator<Item = &UserId>,
|
||||
) -> OlmResult<BTreeMap<UserId, BTreeMap<Box<DeviceId>, DeviceKeyAlgorithm>>> {
|
||||
) -> OlmResult<Option<(Uuid, KeysClaimRequest)>> {
|
||||
let mut missing = BTreeMap::new();
|
||||
|
||||
for user_id in users {
|
||||
|
@ -353,7 +401,17 @@ impl OlmMachine {
|
|||
}
|
||||
}
|
||||
|
||||
Ok(missing)
|
||||
if missing.is_empty() {
|
||||
Ok(None)
|
||||
} else {
|
||||
Ok(Some((
|
||||
Uuid::new_v4(),
|
||||
KeysClaimRequest {
|
||||
timeout: Some(OlmMachine::KEY_CLAIM_TIMEOUT),
|
||||
one_time_keys: missing,
|
||||
},
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
/// Receive a successful key claim response and create new Olm sessions with
|
||||
|
@ -362,10 +420,7 @@ impl OlmMachine {
|
|||
/// # Arguments
|
||||
///
|
||||
/// * `response` - The response containing the claimed one-time keys.
|
||||
pub async fn receive_keys_claim_response(
|
||||
&self,
|
||||
response: &claim_keys::Response,
|
||||
) -> OlmResult<()> {
|
||||
async fn receive_keys_claim_response(&self, response: &KeysClaimResponse) -> OlmResult<()> {
|
||||
// TODO log the failures here
|
||||
|
||||
for (user_id, user_devices) in &response.one_time_keys {
|
||||
|
@ -429,6 +484,9 @@ impl OlmMachine {
|
|||
let mut changed_devices = Vec::new();
|
||||
|
||||
for (user_id, device_map) in device_keys_map {
|
||||
// TODO move this out into the handle keys query response method
|
||||
// since we might fail handle the new device at any point here or
|
||||
// when updating the user identities.
|
||||
self.store.update_tracked_user(user_id, false).await?;
|
||||
|
||||
for (device_id, device_keys) in device_map.iter() {
|
||||
|
@ -492,6 +550,113 @@ impl OlmMachine {
|
|||
Ok(changed_devices)
|
||||
}
|
||||
|
||||
/// Handle the device keys part of a key query response.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `response` - The keys query response.
|
||||
///
|
||||
/// Returns a list of identities that changed. Changed here means either
|
||||
/// they are new, one of their properties has changed or they got deleted.
|
||||
async fn handle_cross_singing_keys(
|
||||
&self,
|
||||
response: &KeysQueryResponse,
|
||||
) -> StoreResult<Vec<UserIdentities>> {
|
||||
let mut changed = Vec::new();
|
||||
|
||||
for (user_id, master_key) in &response.master_keys {
|
||||
let master_key = MasterPubkey::from(master_key);
|
||||
|
||||
let self_signing = if let Some(s) = response.self_signing_keys.get(user_id) {
|
||||
SelfSigningPubkey::from(s)
|
||||
} else {
|
||||
warn!(
|
||||
"User identity for user {} didn't contain a self signing pubkey",
|
||||
user_id
|
||||
);
|
||||
continue;
|
||||
};
|
||||
|
||||
let identity = if let Some(mut i) = self.store.get_user_identity(user_id).await? {
|
||||
match &mut i {
|
||||
UserIdentities::Own(ref mut identity) => {
|
||||
let user_signing = if let Some(s) = response.user_signing_keys.get(user_id)
|
||||
{
|
||||
UserSigningPubkey::from(s)
|
||||
} else {
|
||||
warn!(
|
||||
"User identity for our own user {} didn't \
|
||||
contain a user signing pubkey",
|
||||
user_id
|
||||
);
|
||||
continue;
|
||||
};
|
||||
|
||||
identity
|
||||
.update(master_key, self_signing, user_signing)
|
||||
.map(|_| i)
|
||||
}
|
||||
UserIdentities::Other(ref mut identity) => {
|
||||
identity.update(master_key, self_signing).map(|_| i)
|
||||
}
|
||||
}
|
||||
} else if user_id == self.user_id() {
|
||||
if let Some(s) = response.user_signing_keys.get(user_id) {
|
||||
let user_signing = UserSigningPubkey::from(s);
|
||||
|
||||
if master_key.user_id() != user_id
|
||||
|| self_signing.user_id() != user_id
|
||||
|| user_signing.user_id() != user_id
|
||||
{
|
||||
warn!(
|
||||
"User id mismatch in one of the cross signing keys for user {}",
|
||||
user_id
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
OwnUserIdentity::new(master_key, self_signing, user_signing)
|
||||
.map(UserIdentities::Own)
|
||||
} else {
|
||||
warn!(
|
||||
"User identity for our own user {} didn't contain a \
|
||||
user signing pubkey",
|
||||
user_id
|
||||
);
|
||||
continue;
|
||||
}
|
||||
} else if master_key.user_id() != user_id || self_signing.user_id() != user_id {
|
||||
warn!(
|
||||
"User id mismatch in one of the cross signing keys for user {}",
|
||||
user_id
|
||||
);
|
||||
continue;
|
||||
} else {
|
||||
UserIdentity::new(master_key, self_signing).map(UserIdentities::Other)
|
||||
};
|
||||
|
||||
match identity {
|
||||
Ok(i) => {
|
||||
trace!(
|
||||
"Updated or created new user identity for {}: {:?}",
|
||||
user_id,
|
||||
i
|
||||
);
|
||||
changed.push(i);
|
||||
}
|
||||
Err(e) => {
|
||||
warn!(
|
||||
"Couldn't update or create new user identity for {}: {:?}",
|
||||
user_id, e
|
||||
);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(changed)
|
||||
}
|
||||
|
||||
/// Receive a successful keys query response.
|
||||
///
|
||||
/// Returns a list of devices newly discovered devices and devices that
|
||||
|
@ -501,31 +666,33 @@ impl OlmMachine {
|
|||
///
|
||||
/// * `response` - The keys query response of the request that the client
|
||||
/// performed.
|
||||
pub async fn receive_keys_query_response(
|
||||
async fn receive_keys_query_response(
|
||||
&self,
|
||||
response: &get_keys::Response,
|
||||
) -> OlmResult<Vec<ReadOnlyDevice>> {
|
||||
response: &KeysQueryResponse,
|
||||
) -> OlmResult<(Vec<ReadOnlyDevice>, Vec<UserIdentities>)> {
|
||||
let changed_devices = self
|
||||
.handle_devices_from_key_query(&response.device_keys)
|
||||
.await?;
|
||||
self.store.save_devices(&changed_devices).await?;
|
||||
let changed_identities = self.handle_cross_singing_keys(response).await?;
|
||||
self.store.save_user_identities(&changed_identities).await?;
|
||||
|
||||
Ok(changed_devices)
|
||||
Ok((changed_devices, changed_identities))
|
||||
}
|
||||
|
||||
/// Get a request to upload E2EE keys to the server.
|
||||
///
|
||||
/// Returns an empty error if no keys need to be uploaded.
|
||||
/// Returns None if no keys need to be uploaded.
|
||||
///
|
||||
/// The response of a successful key upload requests needs to be passed to
|
||||
/// the [`OlmMachine`] with the [`receive_keys_upload_response`].
|
||||
///
|
||||
/// [`receive_keys_upload_response`]: #method.receive_keys_upload_response
|
||||
/// [`OlmMachine`]: struct.OlmMachine.html
|
||||
pub async fn keys_for_upload(&self) -> StdResult<upload_keys::Request, ()> {
|
||||
async fn keys_for_upload(&self) -> Option<upload_keys::Request> {
|
||||
let (device_keys, one_time_keys) = self.account.keys_for_upload().await?;
|
||||
|
||||
Ok(upload_keys::Request {
|
||||
Some(upload_keys::Request {
|
||||
device_keys,
|
||||
one_time_keys,
|
||||
})
|
||||
|
@ -663,7 +830,7 @@ impl OlmMachine {
|
|||
|
||||
trace!("Successfully decrypted a Olm message: {}", plaintext);
|
||||
|
||||
Ok(self.parse_decrypted_to_device_event(sender, &plaintext)?)
|
||||
self.parse_decrypted_to_device_event(sender, &plaintext)
|
||||
}
|
||||
|
||||
/// Parse a decrypted Olm message, check that the plaintext and encrypted
|
||||
|
@ -908,53 +1075,6 @@ impl OlmMachine {
|
|||
Ok(session.encrypt(content).await)
|
||||
}
|
||||
|
||||
/// Encrypt the given event for the given Device
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `reciepient_device` - The device that the event should be encrypted
|
||||
/// for.
|
||||
///
|
||||
/// * `event_type` - The type of the event.
|
||||
///
|
||||
/// * `content` - The content of the event that should be encrypted.
|
||||
async fn olm_encrypt(
|
||||
&self,
|
||||
recipient_device: &ReadOnlyDevice,
|
||||
event_type: EventType,
|
||||
content: Value,
|
||||
) -> OlmResult<EncryptedEventContent> {
|
||||
let sender_key = if let Some(k) = recipient_device.get_key(DeviceKeyAlgorithm::Curve25519) {
|
||||
k
|
||||
} else {
|
||||
warn!(
|
||||
"Trying to encrypt a Megolm session for user {} on device {}, \
|
||||
but the device doesn't have a curve25519 key",
|
||||
recipient_device.user_id(),
|
||||
recipient_device.device_id()
|
||||
);
|
||||
return Err(EventError::MissingSenderKey.into());
|
||||
};
|
||||
|
||||
let mut session = if let Some(s) = self.store.get_sessions(sender_key).await? {
|
||||
let session = &s.lock().await[0];
|
||||
session.clone()
|
||||
} else {
|
||||
warn!(
|
||||
"Trying to encrypt a Megolm session for user {} on device {}, \
|
||||
but no Olm session is found",
|
||||
recipient_device.user_id(),
|
||||
recipient_device.device_id()
|
||||
);
|
||||
return Err(OlmError::MissingSession);
|
||||
};
|
||||
|
||||
let message = session.encrypt(recipient_device, event_type, content).await;
|
||||
self.store.save_sessions(&[session]).await?;
|
||||
|
||||
message
|
||||
}
|
||||
|
||||
/// Should the client share a group session for the given room.
|
||||
///
|
||||
/// Returns true if a session needs to be shared before room messages can be
|
||||
|
@ -989,15 +1109,12 @@ impl OlmMachine {
|
|||
/// used.
|
||||
///
|
||||
/// `users` - The list of users that should receive the group session.
|
||||
pub async fn share_group_session<S>(
|
||||
pub async fn share_group_session(
|
||||
&self,
|
||||
room_id: &RoomId,
|
||||
users: impl Iterator<Item = &UserId>,
|
||||
encryption_settings: S,
|
||||
) -> OlmResult<Vec<OwnedToDeviceRequest>>
|
||||
where
|
||||
S: Into<EncryptionSettings> + Sized,
|
||||
{
|
||||
encryption_settings: impl Into<EncryptionSettings>,
|
||||
) -> OlmResult<Vec<OwnedToDeviceRequest>> {
|
||||
self.create_outbound_group_session(room_id, encryption_settings.into())
|
||||
.await?;
|
||||
let session = self.outbound_group_sessions.get(room_id).unwrap();
|
||||
|
@ -1015,7 +1132,7 @@ impl OlmMachine {
|
|||
let mut devices = Vec::new();
|
||||
|
||||
for user_id in users {
|
||||
for device in self.store.get_user_devices(user_id).await?.devices() {
|
||||
for device in self.get_user_devices(user_id).await?.devices() {
|
||||
if !device.is_blacklisted() {
|
||||
devices.push(device.clone());
|
||||
}
|
||||
|
@ -1029,8 +1146,8 @@ impl OlmMachine {
|
|||
let mut messages = BTreeMap::new();
|
||||
|
||||
for device in device_map_chunk {
|
||||
let encrypted = self
|
||||
.olm_encrypt(&device, EventType::RoomKey, key_content.clone())
|
||||
let encrypted = device
|
||||
.encrypt(EventType::RoomKey, key_content.clone())
|
||||
.await;
|
||||
|
||||
let encrypted = match encrypted {
|
||||
|
@ -1042,16 +1159,13 @@ impl OlmMachine {
|
|||
Err(e) => return Err(e),
|
||||
};
|
||||
|
||||
if !messages.contains_key(device.user_id()) {
|
||||
messages.insert(device.user_id().clone(), BTreeMap::new());
|
||||
};
|
||||
|
||||
let user_messages = messages.get_mut(device.user_id()).unwrap();
|
||||
|
||||
user_messages.insert(
|
||||
DeviceIdOrAllDevices::DeviceId(device.device_id().into()),
|
||||
serde_json::value::to_raw_value(&encrypted)?,
|
||||
);
|
||||
messages
|
||||
.entry(device.user_id().clone())
|
||||
.or_insert_with(BTreeMap::new)
|
||||
.insert(
|
||||
DeviceIdOrAllDevices::DeviceId(device.device_id().into()),
|
||||
serde_json::value::to_raw_value(&encrypted)?,
|
||||
);
|
||||
}
|
||||
|
||||
requests.push(OwnedToDeviceRequest {
|
||||
|
@ -1122,12 +1236,12 @@ impl OlmMachine {
|
|||
}
|
||||
|
||||
/// Get the to-device requests that need to be sent out.
|
||||
pub fn outgoing_to_device_requests(&self) -> Vec<OwnedToDeviceRequest> {
|
||||
fn outgoing_to_device_requests(&self) -> Vec<OutgoingRequest> {
|
||||
self.verification_machine.outgoing_to_device_requests()
|
||||
}
|
||||
|
||||
/// Mark an outgoing to-device requests as sent.
|
||||
pub fn mark_to_device_request_as_sent(&self, request_id: &str) {
|
||||
fn mark_to_device_request_as_sent(&self, request_id: &Uuid) {
|
||||
self.verification_machine.mark_requests_as_sent(request_id);
|
||||
}
|
||||
|
||||
|
@ -1271,10 +1385,7 @@ impl OlmMachine {
|
|||
///
|
||||
/// If the user is already known to the Olm machine it will not be
|
||||
/// considered for a key query.
|
||||
pub async fn update_tracked_users<'a, I>(&self, users: I)
|
||||
where
|
||||
I: IntoIterator<Item = &'a UserId>,
|
||||
{
|
||||
pub async fn update_tracked_users(&self, users: impl IntoIterator<Item = &UserId>) {
|
||||
for user in users {
|
||||
if self.store.is_user_tracked(user) {
|
||||
continue;
|
||||
|
@ -1286,22 +1397,34 @@ impl OlmMachine {
|
|||
}
|
||||
}
|
||||
|
||||
/// Should the client perform a key query request.
|
||||
pub async fn should_query_keys(&self) -> bool {
|
||||
self.store.has_users_for_key_query()
|
||||
}
|
||||
|
||||
/// Get the set of users that we need to query keys for.
|
||||
/// Get a key query request if one is needed.
|
||||
///
|
||||
/// Returns a hash set of users that need to be queried for keys.
|
||||
/// Returns a key query reqeust if the client should query E2E keys,
|
||||
/// otherwise None.
|
||||
///
|
||||
/// The response of a successful key query requests needs to be passed to
|
||||
/// the [`OlmMachine`] with the [`receive_keys_query_response`].
|
||||
///
|
||||
/// [`OlmMachine`]: struct.OlmMachine.html
|
||||
/// [`receive_keys_query_response`]: #method.receive_keys_query_response
|
||||
pub async fn users_for_key_query(&self) -> HashSet<UserId> {
|
||||
self.store.users_for_key_query()
|
||||
async fn users_for_key_query(&self) -> Option<KeysQueryRequest> {
|
||||
let mut users = self.store.users_for_key_query();
|
||||
|
||||
if users.is_empty() {
|
||||
None
|
||||
} else {
|
||||
let mut device_keys: BTreeMap<UserId, Vec<Box<DeviceId>>> = BTreeMap::new();
|
||||
|
||||
for user in users.drain() {
|
||||
device_keys.insert(user, Vec::new());
|
||||
}
|
||||
|
||||
Some(KeysQueryRequest {
|
||||
timeout: None,
|
||||
device_keys,
|
||||
token: None,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Get a specific device of a user.
|
||||
|
@ -1338,9 +1461,21 @@ impl OlmMachine {
|
|||
.ok()
|
||||
.flatten()?;
|
||||
|
||||
let own_identity = self
|
||||
.store
|
||||
.get_user_identity(self.user_id())
|
||||
.await
|
||||
.ok()
|
||||
.flatten()
|
||||
.map(|i| i.own().cloned())
|
||||
.flatten();
|
||||
let device_owner_identity = self.store.get_user_identity(user_id).await.ok().flatten();
|
||||
|
||||
Some(Device {
|
||||
inner: device,
|
||||
verification_machine: self.verification_machine.clone(),
|
||||
own_identity,
|
||||
device_owner_identity,
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -1370,9 +1505,21 @@ impl OlmMachine {
|
|||
pub async fn get_user_devices(&self, user_id: &UserId) -> StoreResult<UserDevices> {
|
||||
let devices = self.store.get_user_devices(user_id).await?;
|
||||
|
||||
let own_identity = self
|
||||
.store
|
||||
.get_user_identity(self.user_id())
|
||||
.await
|
||||
.ok()
|
||||
.flatten()
|
||||
.map(|i| i.own().cloned())
|
||||
.flatten();
|
||||
let device_owner_identity = self.store.get_user_identity(user_id).await.ok().flatten();
|
||||
|
||||
Ok(UserDevices {
|
||||
inner: devices,
|
||||
verification_machine: self.verification_machine.clone(),
|
||||
own_identity,
|
||||
device_owner_identity,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -1381,7 +1528,6 @@ impl OlmMachine {
|
|||
pub(crate) mod test {
|
||||
static USER_ID: &str = "@bob:example.org";
|
||||
|
||||
use matrix_sdk_common::js_int::uint;
|
||||
use std::{
|
||||
collections::BTreeMap,
|
||||
convert::{TryFrom, TryInto},
|
||||
|
@ -1390,16 +1536,20 @@ pub(crate) mod test {
|
|||
|
||||
use http::Response;
|
||||
use serde_json::json;
|
||||
#[cfg(feature = "sqlite_cryptostore")]
|
||||
use tempfile::tempdir;
|
||||
|
||||
use crate::{
|
||||
machine::{OlmMachine, OneTimeKeys},
|
||||
verification::test::request_to_event,
|
||||
machine::OlmMachine,
|
||||
verification::test::{outgoing_request_to_event, request_to_event},
|
||||
verify_json, EncryptionSettings, ReadOnlyDevice,
|
||||
};
|
||||
|
||||
use matrix_sdk_common::{
|
||||
api::r0::{keys, to_device::send_event_to_device::IncomingRequest as OwnedToDeviceRequest},
|
||||
api::r0::{
|
||||
keys::{claim_keys, get_keys, upload_keys, OneTimeKey},
|
||||
to_device::send_event_to_device::IncomingRequest as OwnedToDeviceRequest,
|
||||
},
|
||||
events::{
|
||||
room::{
|
||||
encrypted::EncryptedEventContent,
|
||||
|
@ -1415,6 +1565,11 @@ pub(crate) mod test {
|
|||
};
|
||||
use matrix_sdk_test::test_json;
|
||||
|
||||
/// These keys need to be periodically uploaded to the server.
|
||||
type OneTimeKeys = BTreeMap<DeviceKeyId, OneTimeKey>;
|
||||
|
||||
use matrix_sdk_common::js_int::uint;
|
||||
|
||||
fn alice_id() -> UserId {
|
||||
user_id!("@alice:example.org")
|
||||
}
|
||||
|
@ -1434,14 +1589,14 @@ pub(crate) mod test {
|
|||
.unwrap()
|
||||
}
|
||||
|
||||
fn keys_upload_response() -> keys::upload_keys::Response {
|
||||
fn keys_upload_response() -> upload_keys::Response {
|
||||
let data = response_from_file(&test_json::KEYS_UPLOAD);
|
||||
keys::upload_keys::Response::try_from(data).expect("Can't parse the keys upload response")
|
||||
upload_keys::Response::try_from(data).expect("Can't parse the keys upload response")
|
||||
}
|
||||
|
||||
fn keys_query_response() -> keys::get_keys::Response {
|
||||
fn keys_query_response() -> get_keys::Response {
|
||||
let data = response_from_file(&test_json::KEYS_QUERY);
|
||||
keys::get_keys::Response::try_from(data).expect("Can't parse the keys upload response")
|
||||
get_keys::Response::try_from(data).expect("Can't parse the keys upload response")
|
||||
}
|
||||
|
||||
fn to_device_requests_to_content(requests: Vec<OwnedToDeviceRequest>) -> EncryptedEventContent {
|
||||
|
@ -1519,7 +1674,7 @@ pub(crate) mod test {
|
|||
let mut one_time_keys = BTreeMap::new();
|
||||
one_time_keys.insert(bob.user_id.clone(), bob_keys);
|
||||
|
||||
let response = keys::claim_keys::Response {
|
||||
let response = claim_keys::Response {
|
||||
failures: BTreeMap::new(),
|
||||
one_time_keys,
|
||||
};
|
||||
|
@ -1533,16 +1688,14 @@ pub(crate) mod test {
|
|||
let (alice, bob) = get_machine_pair_with_session().await;
|
||||
|
||||
let bob_device = alice
|
||||
.store
|
||||
.get_device(&bob.user_id, &bob.device_id)
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
|
||||
let event = ToDeviceEvent {
|
||||
sender: alice.user_id.clone(),
|
||||
content: alice
|
||||
.olm_encrypt(&bob_device, EventType::Dummy, json!({}))
|
||||
content: bob_device
|
||||
.encrypt(EventType::Dummy, json!({}))
|
||||
.await
|
||||
.unwrap(),
|
||||
};
|
||||
|
@ -1730,7 +1883,7 @@ pub(crate) mod test {
|
|||
.unwrap();
|
||||
|
||||
let ret = machine.keys_for_upload().await;
|
||||
assert!(ret.is_err());
|
||||
assert!(ret.is_none());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
|
@ -1765,13 +1918,14 @@ pub(crate) mod test {
|
|||
let alice = alice_id();
|
||||
let alice_device = alice_device_id();
|
||||
|
||||
let missing_sessions = machine
|
||||
let (_, missing_sessions) = machine
|
||||
.get_missing_sessions([alice.clone()].iter())
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
|
||||
assert!(missing_sessions.contains_key(&alice));
|
||||
let user_sessions = missing_sessions.get(&alice).unwrap();
|
||||
assert!(missing_sessions.one_time_keys.contains_key(&alice));
|
||||
let user_sessions = missing_sessions.one_time_keys.get(&alice).unwrap();
|
||||
assert!(user_sessions.contains_key(&alice_device));
|
||||
}
|
||||
|
||||
|
@ -1789,7 +1943,7 @@ pub(crate) mod test {
|
|||
let mut one_time_keys = BTreeMap::new();
|
||||
one_time_keys.insert(bob_machine.user_id.clone(), bob_keys);
|
||||
|
||||
let response = keys::claim_keys::Response {
|
||||
let response = claim_keys::Response {
|
||||
failures: BTreeMap::new(),
|
||||
one_time_keys,
|
||||
};
|
||||
|
@ -1814,16 +1968,14 @@ pub(crate) mod test {
|
|||
let (alice, bob) = get_machine_pair_with_session().await;
|
||||
|
||||
let bob_device = alice
|
||||
.store
|
||||
.get_device(&bob.user_id, &bob.device_id)
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
|
||||
let event = ToDeviceEvent {
|
||||
sender: alice.user_id.clone(),
|
||||
content: alice
|
||||
.olm_encrypt(&bob_device, EventType::Dummy, json!({}))
|
||||
content: bob_device
|
||||
.encrypt(EventType::Dummy, json!({}))
|
||||
.await
|
||||
.unwrap(),
|
||||
};
|
||||
|
@ -1950,6 +2102,7 @@ pub(crate) mod test {
|
|||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[cfg(feature = "sqlite_cryptostore")]
|
||||
async fn test_machine_with_default_store() {
|
||||
let tmpdir = tempdir().unwrap();
|
||||
|
||||
|
@ -1998,7 +2151,7 @@ pub(crate) mod test {
|
|||
|
||||
assert!(!bob_device.is_trusted());
|
||||
|
||||
let (alice_sas, request) = bob_device.start_verification();
|
||||
let (alice_sas, request) = bob_device.start_verification().await.unwrap();
|
||||
|
||||
let mut event = request_to_event(alice.user_id(), &request);
|
||||
bob.handle_verification_event(&mut event).await;
|
||||
|
@ -2019,7 +2172,7 @@ pub(crate) mod test {
|
|||
.outgoing_to_device_requests()
|
||||
.iter()
|
||||
.next()
|
||||
.map(|r| request_to_event(alice.user_id(), &r))
|
||||
.map(|r| outgoing_request_to_event(alice.user_id(), r))
|
||||
.unwrap();
|
||||
bob.handle_verification_event(&mut event).await;
|
||||
|
||||
|
@ -2027,7 +2180,7 @@ pub(crate) mod test {
|
|||
.outgoing_to_device_requests()
|
||||
.iter()
|
||||
.next()
|
||||
.map(|r| request_to_event(bob.user_id(), &r))
|
||||
.map(|r| outgoing_request_to_event(bob.user_id(), r))
|
||||
.unwrap();
|
||||
alice.handle_verification_event(&mut event).await;
|
||||
|
||||
|
|
|
@ -12,6 +12,11 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Collection of small in-memory stores that can be used to cache Olm objects.
|
||||
//!
|
||||
//! Note: You'll only be interested in these if you are implementing a custom
|
||||
//! `CryptoStore`.
|
||||
|
||||
use std::{collections::HashMap, sync::Arc};
|
||||
|
||||
use dashmap::{DashMap, ReadOnlyView};
|
||||
|
@ -44,16 +49,15 @@ impl SessionStore {
|
|||
/// Returns true if the the session was added, false if the session was
|
||||
/// already in the store.
|
||||
pub async fn add(&self, session: Session) -> bool {
|
||||
if !self.entries.contains_key(&*session.sender_key) {
|
||||
self.entries.insert(
|
||||
session.sender_key.to_string(),
|
||||
Arc::new(Mutex::new(Vec::new())),
|
||||
);
|
||||
}
|
||||
let sessions = self.entries.get_mut(&*session.sender_key).unwrap();
|
||||
let sessions_lock = self
|
||||
.entries
|
||||
.entry(session.sender_key.to_string())
|
||||
.or_insert_with(|| Arc::new(Mutex::new(Vec::new())));
|
||||
|
||||
if !sessions.lock().await.contains(&session) {
|
||||
sessions.lock().await.push(session);
|
||||
let mut sessions = sessions_lock.lock().await;
|
||||
|
||||
if !sessions.contains(&session) {
|
||||
sessions.push(session);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
|
@ -93,22 +97,13 @@ impl GroupSessionStore {
|
|||
/// Returns true if the the session was added, false if the session was
|
||||
/// already in the store.
|
||||
pub fn add(&self, session: InboundGroupSession) -> bool {
|
||||
if !self.entries.contains_key(&session.room_id) {
|
||||
let room_id = &*session.room_id;
|
||||
self.entries.insert(room_id.clone(), HashMap::new());
|
||||
}
|
||||
|
||||
let mut room_map = self.entries.get_mut(&session.room_id).unwrap();
|
||||
|
||||
if !room_map.contains_key(&*session.sender_key) {
|
||||
let sender_key = &*session.sender_key;
|
||||
room_map.insert(sender_key.to_owned(), HashMap::new());
|
||||
}
|
||||
|
||||
let sender_map = room_map.get_mut(&*session.sender_key).unwrap();
|
||||
let ret = sender_map.insert(session.session_id().to_owned(), session);
|
||||
|
||||
ret.is_none()
|
||||
self.entries
|
||||
.entry((&*session.room_id).clone())
|
||||
.or_insert_with(HashMap::new)
|
||||
.entry(session.sender_key.to_string())
|
||||
.or_insert_with(HashMap::new)
|
||||
.insert(session.session_id().to_owned(), session)
|
||||
.is_none()
|
||||
}
|
||||
|
||||
/// Get a inbound group session from our store.
|
||||
|
@ -173,13 +168,9 @@ impl DeviceStore {
|
|||
/// Returns true if the device was already in the store, false otherwise.
|
||||
pub fn add(&self, device: ReadOnlyDevice) -> bool {
|
||||
let user_id = device.user_id();
|
||||
|
||||
if !self.entries.contains_key(&user_id) {
|
||||
self.entries.insert(user_id.clone(), DashMap::new());
|
||||
}
|
||||
let device_map = self.entries.get_mut(&user_id).unwrap();
|
||||
|
||||
device_map
|
||||
self.entries
|
||||
.entry(user_id.to_owned())
|
||||
.or_insert_with(DashMap::new)
|
||||
.insert(device.device_id().into(), device)
|
||||
.is_none()
|
||||
}
|
||||
|
@ -203,11 +194,13 @@ impl DeviceStore {
|
|||
|
||||
/// Get a read-only view over all devices of the given user.
|
||||
pub fn user_devices(&self, user_id: &UserId) -> ReadOnlyUserDevices {
|
||||
if !self.entries.contains_key(user_id) {
|
||||
self.entries.insert(user_id.clone(), DashMap::new());
|
||||
}
|
||||
ReadOnlyUserDevices {
|
||||
entries: self.entries.get(user_id).unwrap().clone().into_read_only(),
|
||||
entries: self
|
||||
.entries
|
||||
.entry(user_id.clone())
|
||||
.or_insert_with(DashMap::new)
|
||||
.clone()
|
||||
.into_read_only(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -200,18 +200,15 @@ impl Account {
|
|||
|
||||
/// Get a tuple of device and one-time keys that need to be uploaded.
|
||||
///
|
||||
/// Returns an empty error if no keys need to be uploaded.
|
||||
/// Returns None if no keys need to be uploaded.
|
||||
pub(crate) async fn keys_for_upload(
|
||||
&self,
|
||||
) -> Result<
|
||||
(
|
||||
Option<DeviceKeys>,
|
||||
Option<BTreeMap<DeviceKeyId, OneTimeKey>>,
|
||||
),
|
||||
(),
|
||||
> {
|
||||
) -> Option<(
|
||||
Option<DeviceKeys>,
|
||||
Option<BTreeMap<DeviceKeyId, OneTimeKey>>,
|
||||
)> {
|
||||
if !self.should_upload_keys().await {
|
||||
return Err(());
|
||||
return None;
|
||||
}
|
||||
|
||||
let device_keys = if !self.shared() {
|
||||
|
@ -222,7 +219,7 @@ impl Account {
|
|||
|
||||
let one_time_keys = self.signed_one_time_keys().await.ok();
|
||||
|
||||
Ok((device_keys, one_time_keys))
|
||||
Some((device_keys, one_time_keys))
|
||||
}
|
||||
|
||||
/// Mark the current set of one-time keys as being published.
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
// limitations under the License.
|
||||
|
||||
use std::{
|
||||
cmp::min,
|
||||
convert::TryInto,
|
||||
fmt,
|
||||
sync::{
|
||||
|
@ -406,7 +407,11 @@ impl OutboundGroupSession {
|
|||
let count = self.message_count.load(Ordering::SeqCst);
|
||||
|
||||
count >= self.settings.rotation_period_msgs
|
||||
|| self.creation_time.elapsed() >= self.settings.rotation_period
|
||||
|| self.creation_time.elapsed()
|
||||
// Since the encryption settings are provided by users and not
|
||||
// checked someone could set a really low rotation perdiod so
|
||||
// clamp it at a minute.
|
||||
>= min(self.settings.rotation_period, Duration::from_secs(3600))
|
||||
}
|
||||
|
||||
/// Mark the session as shared.
|
||||
|
@ -471,7 +476,10 @@ impl std::fmt::Debug for OutboundGroupSession {
|
|||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use std::{thread::sleep, time::Duration};
|
||||
use std::{
|
||||
sync::Arc,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
use matrix_sdk_common::{
|
||||
events::room::message::{MessageEventContent, TextMessageEventContent},
|
||||
|
@ -482,6 +490,7 @@ mod test {
|
|||
use crate::Account;
|
||||
|
||||
#[tokio::test]
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
async fn expiration() {
|
||||
let settings = EncryptionSettings {
|
||||
rotation_period_msgs: 1,
|
||||
|
@ -507,13 +516,13 @@ mod test {
|
|||
..Default::default()
|
||||
};
|
||||
|
||||
let (session, _) = account
|
||||
let (mut session, _) = account
|
||||
.create_group_session_pair(&room_id!("!test_room:example.org"), settings)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert!(!session.expired());
|
||||
sleep(Duration::from_millis(110));
|
||||
session.creation_time = Arc::new(Instant::now() - Duration::from_secs(60 * 60));
|
||||
assert!(session.expired());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,15 +12,20 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! The crypto specific Olm objects.
|
||||
//!
|
||||
//! Note: You'll only be interested in these if you are implementing a custom
|
||||
//! `CryptoStore`.
|
||||
|
||||
mod account;
|
||||
mod group_sessions;
|
||||
mod session;
|
||||
|
||||
pub use account::{Account, IdentityKeys};
|
||||
pub use group_sessions::{
|
||||
EncryptionSettings, GroupSessionKey, InboundGroupSession, OutboundGroupSession,
|
||||
};
|
||||
pub use session::{OlmMessage, Session};
|
||||
pub use group_sessions::{EncryptionSettings, InboundGroupSession};
|
||||
pub(crate) use group_sessions::{GroupSessionKey, OutboundGroupSession};
|
||||
pub(crate) use session::OlmMessage;
|
||||
pub use session::Session;
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) mod test {
|
||||
|
|
117
matrix_sdk_crypto/src/requests.rs
Normal file
117
matrix_sdk_crypto/src/requests.rs
Normal file
|
@ -0,0 +1,117 @@
|
|||
// 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 std::sync::Arc;
|
||||
|
||||
use matrix_sdk_common::{
|
||||
api::r0::{
|
||||
keys::{
|
||||
claim_keys::Response as KeysClaimResponse,
|
||||
get_keys::{IncomingRequest as KeysQueryRequest, Response as KeysQueryResponse},
|
||||
upload_keys::{Request as KeysUploadRequest, Response as KeysUploadResponse},
|
||||
},
|
||||
to_device::send_event_to_device::{
|
||||
IncomingRequest as ToDeviceRequest, Response as ToDeviceResponse,
|
||||
},
|
||||
},
|
||||
uuid::Uuid,
|
||||
};
|
||||
|
||||
/// TODO
|
||||
#[derive(Debug)]
|
||||
pub enum OutgoingRequests {
|
||||
/// TODO
|
||||
KeysUpload(KeysUploadRequest),
|
||||
/// TODO
|
||||
KeysQuery(KeysQueryRequest),
|
||||
/// TODO
|
||||
ToDeviceRequest(ToDeviceRequest),
|
||||
}
|
||||
|
||||
impl From<KeysQueryRequest> for OutgoingRequests {
|
||||
fn from(request: KeysQueryRequest) -> Self {
|
||||
OutgoingRequests::KeysQuery(request)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<KeysUploadRequest> for OutgoingRequests {
|
||||
fn from(request: KeysUploadRequest) -> Self {
|
||||
OutgoingRequests::KeysUpload(request)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ToDeviceRequest> for OutgoingRequests {
|
||||
fn from(request: ToDeviceRequest) -> Self {
|
||||
OutgoingRequests::ToDeviceRequest(request)
|
||||
}
|
||||
}
|
||||
|
||||
/// TODO
|
||||
#[derive(Debug)]
|
||||
pub enum IncomingResponse<'a> {
|
||||
/// TODO
|
||||
KeysUpload(&'a KeysUploadResponse),
|
||||
/// TODO
|
||||
KeysQuery(&'a KeysQueryResponse),
|
||||
/// TODO
|
||||
ToDevice(&'a ToDeviceResponse),
|
||||
///
|
||||
KeysClaim(&'a KeysClaimResponse),
|
||||
}
|
||||
|
||||
impl<'a> From<&'a KeysUploadResponse> for IncomingResponse<'a> {
|
||||
fn from(response: &'a KeysUploadResponse) -> Self {
|
||||
IncomingResponse::KeysUpload(response)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a KeysQueryResponse> for IncomingResponse<'a> {
|
||||
fn from(response: &'a KeysQueryResponse) -> Self {
|
||||
IncomingResponse::KeysQuery(response)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a ToDeviceResponse> for IncomingResponse<'a> {
|
||||
fn from(response: &'a ToDeviceResponse) -> Self {
|
||||
IncomingResponse::ToDevice(response)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a KeysClaimResponse> for IncomingResponse<'a> {
|
||||
fn from(response: &'a KeysClaimResponse) -> Self {
|
||||
IncomingResponse::KeysClaim(response)
|
||||
}
|
||||
}
|
||||
|
||||
/// TODO
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct OutgoingRequest {
|
||||
/// The unique id of a request, needs to be passed when receiving a
|
||||
/// response.
|
||||
pub(crate) request_id: Uuid,
|
||||
/// TODO
|
||||
pub(crate) request: Arc<OutgoingRequests>,
|
||||
}
|
||||
|
||||
impl OutgoingRequest {
|
||||
/// Get the unique id of this request.
|
||||
pub fn request_id(&self) -> &Uuid {
|
||||
&self.request_id
|
||||
}
|
||||
|
||||
/// Get the underlying outgoing request.
|
||||
pub fn request(&self) -> &OutgoingRequests {
|
||||
&self.request
|
||||
}
|
||||
}
|
|
@ -15,7 +15,7 @@
|
|||
use std::{collections::HashSet, sync::Arc};
|
||||
|
||||
use async_trait::async_trait;
|
||||
use dashmap::DashSet;
|
||||
use dashmap::{DashMap, DashSet};
|
||||
use matrix_sdk_common::{
|
||||
identifiers::{DeviceId, RoomId, UserId},
|
||||
locks::Mutex,
|
||||
|
@ -25,6 +25,7 @@ use super::{Account, CryptoStore, InboundGroupSession, Result, Session};
|
|||
use crate::{
|
||||
device::ReadOnlyDevice,
|
||||
memory_stores::{DeviceStore, GroupSessionStore, ReadOnlyUserDevices, SessionStore},
|
||||
user_identity::UserIdentities,
|
||||
};
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct MemoryStore {
|
||||
|
@ -33,6 +34,7 @@ pub struct MemoryStore {
|
|||
tracked_users: Arc<DashSet<UserId>>,
|
||||
users_for_key_query: Arc<DashSet<UserId>>,
|
||||
devices: DeviceStore,
|
||||
identities: Arc<DashMap<UserId, UserIdentities>>,
|
||||
}
|
||||
|
||||
impl MemoryStore {
|
||||
|
@ -43,6 +45,7 @@ impl MemoryStore {
|
|||
tracked_users: Arc::new(DashSet::new()),
|
||||
users_for_key_query: Arc::new(DashSet::new()),
|
||||
devices: DeviceStore::new(),
|
||||
identities: Arc::new(DashMap::new()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -131,6 +134,20 @@ impl CryptoStore for MemoryStore {
|
|||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn get_user_identity(&self, user_id: &UserId) -> Result<Option<UserIdentities>> {
|
||||
#[allow(clippy::map_clone)]
|
||||
Ok(self.identities.get(user_id).map(|i| i.clone()))
|
||||
}
|
||||
|
||||
async fn save_user_identities(&self, identities: &[UserIdentities]) -> Result<()> {
|
||||
for identity in identities {
|
||||
let _ = self
|
||||
.identities
|
||||
.insert(identity.user_id().to_owned(), identity.clone());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
|
@ -30,6 +30,7 @@ use super::{
|
|||
device::ReadOnlyDevice,
|
||||
memory_stores::ReadOnlyUserDevices,
|
||||
olm::{Account, InboundGroupSession, Session},
|
||||
user_identity::UserIdentities,
|
||||
};
|
||||
|
||||
pub mod memorystore;
|
||||
|
@ -197,4 +198,18 @@ pub trait CryptoStore: Debug {
|
|||
///
|
||||
/// * `user_id` - The user for which we should get all the devices.
|
||||
async fn get_user_devices(&self, user_id: &UserId) -> Result<ReadOnlyUserDevices>;
|
||||
|
||||
/// Save the given user identities in the store.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `identities` - The identities that should be saved in the store.
|
||||
async fn save_user_identities(&self, identities: &[UserIdentities]) -> Result<()>;
|
||||
|
||||
/// Get the user identity that is attached to the given user id.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `user_id` - The user for which we should get the identity.
|
||||
async fn get_user_identity(&self, user_id: &UserId) -> Result<Option<UserIdentities>>;
|
||||
}
|
||||
|
|
|
@ -36,8 +36,9 @@ use zeroize::Zeroizing;
|
|||
|
||||
use super::{CryptoStore, CryptoStoreError, Result};
|
||||
use crate::{
|
||||
device::{ReadOnlyDevice, TrustState},
|
||||
device::{LocalTrust, ReadOnlyDevice},
|
||||
memory_stores::{DeviceStore, GroupSessionStore, ReadOnlyUserDevices, SessionStore},
|
||||
user_identity::UserIdentities,
|
||||
Account, IdentityKeys, InboundGroupSession, Session,
|
||||
};
|
||||
|
||||
|
@ -485,7 +486,7 @@ impl SqliteStore {
|
|||
|
||||
let device_id = &row.2.to_string();
|
||||
let display_name = &row.3;
|
||||
let trust_state = TrustState::from(row.4);
|
||||
let trust_state = LocalTrust::from(row.4);
|
||||
|
||||
let algorithm_rows: Vec<(String,)> =
|
||||
query_as("SELECT algorithm FROM algorithms WHERE device_id = ?")
|
||||
|
@ -883,6 +884,14 @@ impl CryptoStore for SqliteStore {
|
|||
async fn get_user_devices(&self, user_id: &UserId) -> Result<ReadOnlyUserDevices> {
|
||||
Ok(self.devices.user_devices(user_id))
|
||||
}
|
||||
|
||||
async fn get_user_identity(&self, _user_id: &UserId) -> Result<Option<UserIdentities>> {
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
async fn save_user_identities(&self, _users: &[UserIdentities]) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(tarpaulin_include))]
|
||||
|
|
|
@ -14,7 +14,10 @@
|
|||
|
||||
use std::{
|
||||
convert::TryFrom,
|
||||
sync::{atomic::AtomicBool, Arc},
|
||||
sync::{
|
||||
atomic::{AtomicBool, Ordering},
|
||||
Arc,
|
||||
},
|
||||
};
|
||||
|
||||
use serde_json::to_value;
|
||||
|
@ -26,13 +29,31 @@ use matrix_sdk_common::{
|
|||
|
||||
use crate::{error::SignatureError, verify_json, ReadOnlyDevice};
|
||||
|
||||
/// Wrapper for a cross signing key marking it as the master key.
|
||||
///
|
||||
/// Master keys are used to sign other cross signing keys, the self signing and
|
||||
/// user signing keys of an user will be signed by their master key.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct MasterPubkey(Arc<CrossSigningKey>);
|
||||
|
||||
/// Wrapper for a cross signing key marking it as a self signing key.
|
||||
///
|
||||
/// Self signing keys are used to sign the user's own devices.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct SelfSigningPubkey(Arc<CrossSigningKey>);
|
||||
|
||||
/// Wrapper for a cross signing key marking it as a user signing key.
|
||||
///
|
||||
/// User signing keys are used to sign the master keys of other users.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct UserSigningPubkey(Arc<CrossSigningKey>);
|
||||
|
||||
impl PartialEq for MasterPubkey {
|
||||
fn eq(&self, other: &MasterPubkey) -> bool {
|
||||
self.0.user_id == other.0.user_id && self.0.keys == other.0.keys
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&CrossSigningKey> for MasterPubkey {
|
||||
fn from(key: &CrossSigningKey) -> Self {
|
||||
Self(Arc::new(key.clone()))
|
||||
|
@ -63,12 +84,24 @@ impl<'a> From<&'a UserSigningPubkey> for CrossSigningSubKeys<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Enum over the cross signing sub-keys.
|
||||
enum CrossSigningSubKeys<'a> {
|
||||
/// The self signing subkey.
|
||||
SelfSigning(&'a SelfSigningPubkey),
|
||||
/// The user signing subkey.
|
||||
UserSigning(&'a UserSigningPubkey),
|
||||
}
|
||||
|
||||
impl<'a> CrossSigningSubKeys<'a> {
|
||||
/// Get the id of the user that owns this cross signing subkey.
|
||||
fn user_id(&self) -> &UserId {
|
||||
match self {
|
||||
CrossSigningSubKeys::SelfSigning(key) => &key.0.user_id,
|
||||
CrossSigningSubKeys::UserSigning(key) => &key.0.user_id,
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the `CrossSigningKey` from an sub-keys enum
|
||||
fn cross_signing_key(&self) -> &CrossSigningKey {
|
||||
match self {
|
||||
CrossSigningSubKeys::SelfSigning(key) => &key.0,
|
||||
|
@ -77,13 +110,29 @@ impl<'a> CrossSigningSubKeys<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
pub struct UserIdentity {
|
||||
user_id: Arc<UserId>,
|
||||
master_key: MasterPubkey,
|
||||
self_signing_key: SelfSigningPubkey,
|
||||
}
|
||||
|
||||
impl MasterPubkey {
|
||||
/// Get the user id of the master key's owner.
|
||||
pub fn user_id(&self) -> &UserId {
|
||||
&self.0.user_id
|
||||
}
|
||||
|
||||
/// Get the master key with the given key id.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `key_id` - The id of the key that should be fetched.
|
||||
pub fn get_key(&self, key_id: &DeviceKeyId) -> Option<&str> {
|
||||
self.0.keys.get(key_id.as_str()).map(|k| k.as_str())
|
||||
}
|
||||
|
||||
/// Check if the given cross signing sub-key is signed by the master key.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `subkey` - The subkey that should be checked for a valid signature.
|
||||
///
|
||||
/// Returns an empty result if the signature check succeeded, otherwise a
|
||||
/// SignatureError indicating why the check failed.
|
||||
fn verify_subkey<'a>(
|
||||
&self,
|
||||
subkey: impl Into<CrossSigningSubKeys<'a>>,
|
||||
|
@ -95,16 +144,21 @@ impl MasterPubkey {
|
|||
.next()
|
||||
.ok_or(SignatureError::MissingSigningKey)?;
|
||||
|
||||
let key_id = DeviceKeyId::try_from(key_id.as_str())?;
|
||||
|
||||
// FIXME `KeyUsage is missing PartialEq.
|
||||
// if self.0.usage.contains(&KeyUsage::Master) {
|
||||
// return Err(SignatureError::MissingSigningKey);
|
||||
// }
|
||||
|
||||
let subkey: CrossSigningSubKeys = subkey.into();
|
||||
|
||||
if &self.0.user_id != subkey.user_id() {
|
||||
return Err(SignatureError::UserIdMissmatch);
|
||||
}
|
||||
|
||||
verify_json(
|
||||
&self.0.user_id,
|
||||
&DeviceKeyId::try_from(key_id.as_str())?,
|
||||
&key_id,
|
||||
key,
|
||||
&mut to_value(subkey.cross_signing_key()).map_err(|_| SignatureError::NotAnObject)?,
|
||||
)
|
||||
|
@ -112,6 +166,20 @@ impl MasterPubkey {
|
|||
}
|
||||
|
||||
impl UserSigningPubkey {
|
||||
/// Get the user id of the user signing key's owner.
|
||||
pub fn user_id(&self) -> &UserId {
|
||||
&self.0.user_id
|
||||
}
|
||||
|
||||
/// Check if the given master key is signed by this user signing key.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `master_key` - The master key that should be checked for a valid
|
||||
/// signature.
|
||||
///
|
||||
/// Returns an empty result if the signature check succeeded, otherwise a
|
||||
/// SignatureError indicating why the check failed.
|
||||
fn verify_master_key(&self, master_key: &MasterPubkey) -> Result<(), SignatureError> {
|
||||
let (key_id, key) = self
|
||||
.0
|
||||
|
@ -132,6 +200,19 @@ impl UserSigningPubkey {
|
|||
}
|
||||
|
||||
impl SelfSigningPubkey {
|
||||
/// Get the user id of the self signing key's owner.
|
||||
pub fn user_id(&self) -> &UserId {
|
||||
&self.0.user_id
|
||||
}
|
||||
|
||||
/// Check if the given device is signed by this self signing key.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `device` - The device that should be checked for a valid signature.
|
||||
///
|
||||
/// Returns an empty result if the signature check succeeded, otherwise a
|
||||
/// SignatureError indicating why the check failed.
|
||||
fn verify_device(&self, device: &ReadOnlyDevice) -> Result<(), SignatureError> {
|
||||
let (key_id, key) = self
|
||||
.0
|
||||
|
@ -151,7 +232,71 @@ impl SelfSigningPubkey {
|
|||
}
|
||||
}
|
||||
|
||||
/// Enum over the different user identity types we can have.
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum UserIdentities {
|
||||
/// Our own user identity.
|
||||
Own(OwnUserIdentity),
|
||||
/// Identities of other users.
|
||||
Other(UserIdentity),
|
||||
}
|
||||
|
||||
impl UserIdentities {
|
||||
/// 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(),
|
||||
}
|
||||
}
|
||||
|
||||
/// 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(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Destructure the enum into an `OwnUserIdentity` if it's of the correct
|
||||
/// type.
|
||||
pub fn own(&self) -> Option<&OwnUserIdentity> {
|
||||
match self {
|
||||
UserIdentities::Own(i) => Some(i),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for UserIdentities {
|
||||
fn eq(&self, other: &UserIdentities) -> bool {
|
||||
self.user_id() == other.user_id()
|
||||
}
|
||||
}
|
||||
|
||||
/// 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.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct UserIdentity {
|
||||
user_id: Arc<UserId>,
|
||||
master_key: MasterPubkey,
|
||||
self_signing_key: SelfSigningPubkey,
|
||||
}
|
||||
|
||||
impl UserIdentity {
|
||||
/// Create a new user identity with the given master and self signing key.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `master_key` - The master key of the user identity.
|
||||
///
|
||||
/// * `self signing key` - The self signing key of user identity.
|
||||
///
|
||||
/// Returns a `SignatureError` if the self signing key fails to be correctly
|
||||
/// verified by the given master key.
|
||||
pub fn new(
|
||||
master_key: MasterPubkey,
|
||||
self_signing_key: SelfSigningPubkey,
|
||||
|
@ -165,11 +310,67 @@ impl UserIdentity {
|
|||
})
|
||||
}
|
||||
|
||||
/// Get the user id of this identity.
|
||||
pub fn user_id(&self) -> &UserId {
|
||||
&self.user_id
|
||||
}
|
||||
|
||||
/// Get the public master key of the identity.
|
||||
pub fn master_key(&self) -> &MasterPubkey {
|
||||
&self.master_key
|
||||
}
|
||||
|
||||
/// Update the identity with a new master key and self signing key.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `master_key` - The new master key of the user identity.
|
||||
///
|
||||
/// * `self_signing_key` - The new self signing key of user identity.
|
||||
///
|
||||
/// Returns a `SignatureError` if we failed to update the identity.
|
||||
pub fn update(
|
||||
&mut self,
|
||||
master_key: MasterPubkey,
|
||||
self_signing_key: SelfSigningPubkey,
|
||||
) -> Result<(), SignatureError> {
|
||||
master_key.verify_subkey(&self_signing_key)?;
|
||||
|
||||
self.master_key = master_key;
|
||||
self.self_signing_key = self_signing_key;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Check if the given device has been signed by this identity.
|
||||
///
|
||||
/// The user_id of the user identity and the user_id of the device need to
|
||||
/// match for the signature check to succeed as we don't trust users to sign
|
||||
/// devices of other users.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `device` - The device that should be checked for a valid signature.
|
||||
///
|
||||
/// Returns an empty result if the signature check succeeded, otherwise a
|
||||
/// SignatureError indicating why the check failed.
|
||||
pub fn is_device_signed(&self, device: &ReadOnlyDevice) -> Result<(), SignatureError> {
|
||||
if self.user_id() != device.user_id() {
|
||||
return Err(SignatureError::UserIdMissmatch);
|
||||
}
|
||||
|
||||
self.self_signing_key.verify_device(device)
|
||||
}
|
||||
}
|
||||
|
||||
/// Struct representing a cross signing identity of our own user.
|
||||
///
|
||||
/// This is the user identity of our own user. This user identity will contain a
|
||||
/// master key, self signing key as well as a user signing key.
|
||||
///
|
||||
/// This identity can verify other identities as well as devices belonging to
|
||||
/// the identity.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct OwnUserIdentity {
|
||||
user_id: Arc<UserId>,
|
||||
master_key: MasterPubkey,
|
||||
|
@ -179,6 +380,19 @@ pub struct OwnUserIdentity {
|
|||
}
|
||||
|
||||
impl OwnUserIdentity {
|
||||
/// Create a new own user identity with the given master, self signing, and
|
||||
/// user signing key.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `master_key` - The master key of the user identity.
|
||||
///
|
||||
/// * `self_signing_key` - The self signing key of user identity.
|
||||
///
|
||||
/// * `user_signing_key` - The user signing key of user identity.
|
||||
///
|
||||
/// Returns a `SignatureError` if the self signing key fails to be correctly
|
||||
/// verified by the given master key.
|
||||
pub fn new(
|
||||
master_key: MasterPubkey,
|
||||
self_signing_key: SelfSigningPubkey,
|
||||
|
@ -196,24 +410,113 @@ impl OwnUserIdentity {
|
|||
})
|
||||
}
|
||||
|
||||
/// Get the user id of this identity.
|
||||
pub fn user_id(&self) -> &UserId {
|
||||
&self.user_id
|
||||
}
|
||||
|
||||
/// Get the public master key of the identity.
|
||||
pub fn master_key(&self) -> &MasterPubkey {
|
||||
&self.master_key
|
||||
}
|
||||
|
||||
/// Check if the given identity has been signed by this identity.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `identity` - The identity of another user that we want to check if
|
||||
/// it's has been signed.
|
||||
///
|
||||
/// 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> {
|
||||
self.user_signing_key
|
||||
.verify_master_key(&identity.master_key)
|
||||
}
|
||||
|
||||
/// Check if the given device has been signed by this identity.
|
||||
///
|
||||
/// Only devices of our own user should be checked with this method, if a
|
||||
/// device of a different user is given the signature check will always fail
|
||||
/// even if a valid signature exists.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `device` - The device that should be checked for a valid signature.
|
||||
///
|
||||
/// Returns an empty result if the signature check succeeded, otherwise a
|
||||
/// SignatureError indicating why the check failed.
|
||||
pub fn is_device_signed(&self, device: &ReadOnlyDevice) -> Result<(), SignatureError> {
|
||||
if self.user_id() != device.user_id() {
|
||||
return Err(SignatureError::UserIdMissmatch);
|
||||
}
|
||||
|
||||
self.self_signing_key.verify_device(device)
|
||||
}
|
||||
|
||||
/// Mark our identity as verified.
|
||||
pub fn mark_as_verified(&self) {
|
||||
self.verified.store(true, Ordering::SeqCst)
|
||||
}
|
||||
|
||||
/// Check if our identity is verified.
|
||||
pub fn is_verified(&self) -> bool {
|
||||
self.verified.load(Ordering::SeqCst)
|
||||
}
|
||||
|
||||
/// Update the identity with a new master key and self signing key.
|
||||
///
|
||||
/// Note: This will reset the verification state if the master keys differ.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `master_key` - The new master key of the user identity.
|
||||
///
|
||||
/// * `self_signing_key` - The new self signing key of user identity.
|
||||
///
|
||||
/// * `user_signing_key` - The new user signing key of user identity.
|
||||
///
|
||||
/// Returns a `SignatureError` if we failed to update the identity.
|
||||
pub fn update(
|
||||
&mut self,
|
||||
master_key: MasterPubkey,
|
||||
self_signing_key: SelfSigningPubkey,
|
||||
user_signing_key: UserSigningPubkey,
|
||||
) -> Result<(), SignatureError> {
|
||||
master_key.verify_subkey(&self_signing_key)?;
|
||||
master_key.verify_subkey(&user_signing_key)?;
|
||||
|
||||
self.self_signing_key = self_signing_key;
|
||||
self.user_signing_key = user_signing_key;
|
||||
|
||||
if self.master_key != master_key {
|
||||
self.verified.store(false, Ordering::SeqCst)
|
||||
}
|
||||
|
||||
self.master_key = master_key;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use serde_json::json;
|
||||
use std::convert::TryFrom;
|
||||
use std::{convert::TryFrom, sync::Arc};
|
||||
|
||||
use matrix_sdk_common::{
|
||||
api::r0::keys::get_keys::Response as KeyQueryResponse, identifiers::user_id,
|
||||
};
|
||||
|
||||
use crate::machine::test::response_from_file;
|
||||
use crate::{
|
||||
device::{Device, ReadOnlyDevice},
|
||||
machine::test::response_from_file,
|
||||
olm::Account,
|
||||
store::memorystore::MemoryStore,
|
||||
verification::VerificationMachine,
|
||||
};
|
||||
|
||||
use super::{OwnUserIdentity, UserIdentity};
|
||||
use super::{OwnUserIdentity, UserIdentities, UserIdentity};
|
||||
|
||||
fn other_key_query() -> KeyQueryResponse {
|
||||
let data = response_from_file(&json!({
|
||||
|
@ -276,61 +579,120 @@ mod test {
|
|||
fn own_key_query() -> KeyQueryResponse {
|
||||
let data = response_from_file(&json!({
|
||||
"device_keys": {
|
||||
},
|
||||
"master_keys": {
|
||||
"@example:localhost": {
|
||||
"keys": {
|
||||
"ed25519:rJ2TAGkEOP6dX41Ksll6cl8K3J48l8s/59zaXyvl2p0": "rJ2TAGkEOP6dX41Ksll6cl8K3J48l8s/59zaXyvl2p0"
|
||||
},
|
||||
"signatures": {
|
||||
"@example:localhost": {
|
||||
"ed25519:WSKKLTJZCL": "ZzJp1wtmRdykXAUEItEjNiFlBrxx8L6/Vaen9am8AuGwlxxJtOkuY4m+4MPLvDPOgavKHLsrRuNLAfCeakMlCQ"
|
||||
}
|
||||
},
|
||||
"usage": [
|
||||
"master"
|
||||
],
|
||||
"user_id": "@example:localhost"
|
||||
"@example:localhost": {
|
||||
"WSKKLTJZCL": {
|
||||
"algorithms": [
|
||||
"m.olm.v1.curve25519-aes-sha2",
|
||||
"m.megolm.v1.aes-sha2"
|
||||
],
|
||||
"device_id": "WSKKLTJZCL",
|
||||
"keys": {
|
||||
"curve25519:WSKKLTJZCL": "wnip2tbJBJxrFayC88NNJpm61TeSNgYcqBH4T9yEDhU",
|
||||
"ed25519:WSKKLTJZCL": "lQ+eshkhgKoo+qp9Qgnj3OX5PBoWMU5M9zbuEevwYqE"
|
||||
},
|
||||
"signatures": {
|
||||
"@example:localhost": {
|
||||
"ed25519:WSKKLTJZCL": "SKpIUnq7QK0xleav0PrIQyKjVm+TgZr7Yi8cKjLeZDtkgyToE2d4/e3Aj79dqOlLB92jFVE4d1cM/Ry04wFwCA",
|
||||
"ed25519:0C8lCBxrvrv/O7BQfsKnkYogHZX3zAgw3RfJuyiq210": "9UGu1iC5YhFCdELGfB29YaV+QE0t/X5UDSsPf4QcdZyXIwyp9zBbHX2lh9vWudNQ+akZpaq7ZRaaM+4TCnw/Ag"
|
||||
}
|
||||
},
|
||||
"user_id": "@example:localhost",
|
||||
"unsigned": {
|
||||
"device_display_name": "Cross signing capable"
|
||||
}
|
||||
},
|
||||
"LVWOVGOXME": {
|
||||
"algorithms": [
|
||||
"m.olm.v1.curve25519-aes-sha2",
|
||||
"m.megolm.v1.aes-sha2"
|
||||
],
|
||||
"device_id": "LVWOVGOXME",
|
||||
"keys": {
|
||||
"curve25519:LVWOVGOXME": "KMfWKUhnDW1D11hNzATs/Ax1FQRsJxKCWzq0NyGtIiI",
|
||||
"ed25519:LVWOVGOXME": "k+NC3L7CBD6fBClcHBrKLOkqCyGNSKhWXiH5Q2STRnA"
|
||||
},
|
||||
"signatures": {
|
||||
"@example:localhost": {
|
||||
"ed25519:LVWOVGOXME": "39Ir5Bttpc5+bQwzLj7rkjm5E5/cp/JTbMJ/t0enj6J5w9MXVBFOUqqM2hpaRaRwILMMpwYbJ8IOGjl0Y/MGAw"
|
||||
}
|
||||
},
|
||||
"user_id": "@example:localhost",
|
||||
"unsigned": {
|
||||
"device_display_name": "Non-cross signing"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"failures": {},
|
||||
"master_keys": {
|
||||
"@example:localhost": {
|
||||
"user_id": "@example:localhost",
|
||||
"usage": [
|
||||
"master"
|
||||
],
|
||||
"keys": {
|
||||
"ed25519:rJ2TAGkEOP6dX41Ksll6cl8K3J48l8s/59zaXyvl2p0": "rJ2TAGkEOP6dX41Ksll6cl8K3J48l8s/59zaXyvl2p0"
|
||||
},
|
||||
"signatures": {
|
||||
"@example:localhost": {
|
||||
"ed25519:WSKKLTJZCL": "ZzJp1wtmRdykXAUEItEjNiFlBrxx8L6/Vaen9am8AuGwlxxJtOkuY4m+4MPLvDPOgavKHLsrRuNLAfCeakMlCQ"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"self_signing_keys": {
|
||||
"@example:localhost": {
|
||||
"user_id": "@example:localhost",
|
||||
"usage": [
|
||||
"self_signing"
|
||||
],
|
||||
"keys": {
|
||||
"ed25519:0C8lCBxrvrv/O7BQfsKnkYogHZX3zAgw3RfJuyiq210": "0C8lCBxrvrv/O7BQfsKnkYogHZX3zAgw3RfJuyiq210"
|
||||
},
|
||||
"signatures": {
|
||||
"@example:localhost": {
|
||||
"keys": {
|
||||
"ed25519:0C8lCBxrvrv/O7BQfsKnkYogHZX3zAgw3RfJuyiq210": "0C8lCBxrvrv/O7BQfsKnkYogHZX3zAgw3RfJuyiq210"
|
||||
},
|
||||
"signatures": {
|
||||
"@example:localhost": {
|
||||
"ed25519:rJ2TAGkEOP6dX41Ksll6cl8K3J48l8s/59zaXyvl2p0": "AC7oDUW4rUhtInwb4lAoBJ0wAuu4a5k+8e34B5+NKsDB8HXRwgVwUWN/MRWc/sJgtSbVlhzqS9THEmQQ1C51Bw"
|
||||
}
|
||||
},
|
||||
"usage": [
|
||||
"self_signing"
|
||||
],
|
||||
"user_id": "@example:localhost"
|
||||
"ed25519:rJ2TAGkEOP6dX41Ksll6cl8K3J48l8s/59zaXyvl2p0": "AC7oDUW4rUhtInwb4lAoBJ0wAuu4a5k+8e34B5+NKsDB8HXRwgVwUWN/MRWc/sJgtSbVlhzqS9THEmQQ1C51Bw"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"user_signing_keys": {
|
||||
"@example:localhost": {
|
||||
"user_id": "@example:localhost",
|
||||
"usage": [
|
||||
"user_signing"
|
||||
],
|
||||
"keys": {
|
||||
"ed25519:DU9z4gBFKFKCk7a13sW9wjT0Iyg7Hqv5f0BPM7DEhPo": "DU9z4gBFKFKCk7a13sW9wjT0Iyg7Hqv5f0BPM7DEhPo"
|
||||
},
|
||||
"signatures": {
|
||||
"@example:localhost": {
|
||||
"keys": {
|
||||
"ed25519:DU9z4gBFKFKCk7a13sW9wjT0Iyg7Hqv5f0BPM7DEhPo": "DU9z4gBFKFKCk7a13sW9wjT0Iyg7Hqv5f0BPM7DEhPo"
|
||||
},
|
||||
"signatures": {
|
||||
"@example:localhost": {
|
||||
"ed25519:rJ2TAGkEOP6dX41Ksll6cl8K3J48l8s/59zaXyvl2p0": "C4L2sx9frGqj8w41KyynHGqwUbbwBYRZpYCB+6QWnvQFA5Oi/1PJj8w5anwzEsoO0TWmLYmf7FXuAGewanOWDg"
|
||||
}
|
||||
},
|
||||
"usage": [
|
||||
"user_signing"
|
||||
],
|
||||
"user_id": "@example:localhost"
|
||||
"ed25519:rJ2TAGkEOP6dX41Ksll6cl8K3J48l8s/59zaXyvl2p0": "C4L2sx9frGqj8w41KyynHGqwUbbwBYRZpYCB+6QWnvQFA5Oi/1PJj8w5anwzEsoO0TWmLYmf7FXuAGewanOWDg"
|
||||
}
|
||||
},
|
||||
"failures": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
KeyQueryResponse::try_from(data).expect("Can't parse the keys upload response")
|
||||
}
|
||||
|
||||
fn device(response: &KeyQueryResponse) -> (ReadOnlyDevice, ReadOnlyDevice) {
|
||||
let mut devices = response.device_keys.values().next().unwrap().values();
|
||||
let first = ReadOnlyDevice::try_from(devices.next().unwrap()).unwrap();
|
||||
let second = ReadOnlyDevice::try_from(devices.next().unwrap()).unwrap();
|
||||
(first, second)
|
||||
}
|
||||
|
||||
fn own_identity(response: &KeyQueryResponse) -> OwnUserIdentity {
|
||||
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()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn own_identity_create() {
|
||||
let user_id = user_id!("@example:localhost");
|
||||
|
@ -353,4 +715,43 @@ mod test {
|
|||
|
||||
UserIdentity::new(master_key.into(), self_signing.into()).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn own_identity_check_signatures() {
|
||||
let response = own_key_query();
|
||||
let identity = own_identity(&response);
|
||||
let (first, second) = device(&response);
|
||||
|
||||
assert!(identity.is_device_signed(&first).is_err());
|
||||
assert!(identity.is_device_signed(&second).is_ok());
|
||||
|
||||
let verification_machine = VerificationMachine::new(
|
||||
Account::new(second.user_id(), second.device_id()),
|
||||
Arc::new(Box::new(MemoryStore::new())),
|
||||
);
|
||||
|
||||
let first = Device {
|
||||
inner: first,
|
||||
verification_machine: verification_machine.clone(),
|
||||
own_identity: Some(identity.clone()),
|
||||
device_owner_identity: Some(UserIdentities::Own(identity.clone())),
|
||||
};
|
||||
|
||||
let second = Device {
|
||||
inner: second,
|
||||
verification_machine,
|
||||
own_identity: Some(identity.clone()),
|
||||
device_owner_identity: Some(UserIdentities::Own(identity.clone())),
|
||||
};
|
||||
|
||||
assert!(!second.trust_state());
|
||||
assert!(!second.is_trusted());
|
||||
|
||||
assert!(!first.trust_state());
|
||||
assert!(!first.is_trusted());
|
||||
|
||||
identity.mark_as_verified();
|
||||
assert!(second.trust_state());
|
||||
assert!(!first.trust_state());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,17 +22,18 @@ use matrix_sdk_common::{
|
|||
api::r0::to_device::send_event_to_device::IncomingRequest as OwnedToDeviceRequest,
|
||||
events::{AnyToDeviceEvent, AnyToDeviceEventContent},
|
||||
identifiers::{DeviceId, UserId},
|
||||
uuid::Uuid,
|
||||
};
|
||||
|
||||
use super::sas::{content_to_request, Sas};
|
||||
use crate::{Account, CryptoStore, CryptoStoreError, ReadOnlyDevice};
|
||||
use crate::{requests::OutgoingRequest, Account, CryptoStore, CryptoStoreError, ReadOnlyDevice};
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct VerificationMachine {
|
||||
account: Account,
|
||||
pub(crate) store: Arc<Box<dyn CryptoStore>>,
|
||||
verifications: Arc<DashMap<String, Sas>>,
|
||||
outgoing_to_device_messages: Arc<DashMap<String, OwnedToDeviceRequest>>,
|
||||
outgoing_to_device_messages: Arc<DashMap<Uuid, OutgoingRequest>>,
|
||||
}
|
||||
|
||||
impl VerificationMachine {
|
||||
|
@ -45,9 +46,20 @@ impl VerificationMachine {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn start_sas(&self, device: ReadOnlyDevice) -> (Sas, OwnedToDeviceRequest) {
|
||||
let (sas, content) = Sas::start(self.account.clone(), device.clone(), self.store.clone());
|
||||
let request = content_to_request(
|
||||
pub async fn start_sas(
|
||||
&self,
|
||||
device: ReadOnlyDevice,
|
||||
) -> Result<(Sas, OwnedToDeviceRequest), CryptoStoreError> {
|
||||
let identity = self.store.get_user_identity(device.user_id()).await?;
|
||||
|
||||
let (sas, content) = Sas::start(
|
||||
self.account.clone(),
|
||||
device.clone(),
|
||||
self.store.clone(),
|
||||
identity,
|
||||
);
|
||||
|
||||
let (_, request) = content_to_request(
|
||||
device.user_id(),
|
||||
device.device_id(),
|
||||
AnyToDeviceEventContent::KeyVerificationStart(content),
|
||||
|
@ -56,7 +68,7 @@ impl VerificationMachine {
|
|||
self.verifications
|
||||
.insert(sas.flow_id().to_owned(), sas.clone());
|
||||
|
||||
(sas, request)
|
||||
Ok((sas, request))
|
||||
}
|
||||
|
||||
pub fn get_sas(&self, transaction_id: &str) -> Option<Sas> {
|
||||
|
@ -70,10 +82,14 @@ impl VerificationMachine {
|
|||
recipient_device: &DeviceId,
|
||||
content: AnyToDeviceEventContent,
|
||||
) {
|
||||
let request = content_to_request(recipient, recipient_device, content);
|
||||
let (request_id, request) = content_to_request(recipient, recipient_device, content);
|
||||
|
||||
self.outgoing_to_device_messages
|
||||
.insert(request.txn_id.clone(), request);
|
||||
let request = OutgoingRequest {
|
||||
request_id,
|
||||
request: Arc::new(request.into()),
|
||||
};
|
||||
|
||||
self.outgoing_to_device_messages.insert(request_id, request);
|
||||
}
|
||||
|
||||
fn receive_event_helper(&self, sas: &Sas, event: &mut AnyToDeviceEvent) {
|
||||
|
@ -82,19 +98,15 @@ impl VerificationMachine {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn mark_requests_as_sent(&self, uuid: &str) {
|
||||
pub fn mark_requests_as_sent(&self, uuid: &Uuid) {
|
||||
self.outgoing_to_device_messages.remove(uuid);
|
||||
}
|
||||
|
||||
pub fn outgoing_to_device_requests(&self) -> Vec<OwnedToDeviceRequest> {
|
||||
pub fn outgoing_to_device_requests(&self) -> Vec<OutgoingRequest> {
|
||||
#[allow(clippy::map_clone)]
|
||||
self.outgoing_to_device_messages
|
||||
.iter()
|
||||
.map(|r| OwnedToDeviceRequest {
|
||||
event_type: r.event_type.clone(),
|
||||
txn_id: r.txn_id.clone(),
|
||||
messages: r.messages.clone(),
|
||||
})
|
||||
.map(|r| (*r).clone())
|
||||
.collect()
|
||||
}
|
||||
|
||||
|
@ -104,7 +116,13 @@ impl VerificationMachine {
|
|||
|
||||
for sas in self.verifications.iter() {
|
||||
if let Some(r) = sas.cancel_if_timed_out() {
|
||||
self.outgoing_to_device_messages.insert(r.txn_id.clone(), r);
|
||||
self.outgoing_to_device_messages.insert(
|
||||
r.0,
|
||||
OutgoingRequest {
|
||||
request_id: r.0,
|
||||
request: Arc::new(r.1.into()),
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -128,7 +146,13 @@ impl VerificationMachine {
|
|||
.get_device(&e.sender, &e.content.from_device)
|
||||
.await?
|
||||
{
|
||||
match Sas::from_start_event(self.account.clone(), d, self.store.clone(), e) {
|
||||
match Sas::from_start_event(
|
||||
self.account.clone(),
|
||||
d,
|
||||
self.store.clone(),
|
||||
e,
|
||||
self.store.get_user_identity(&e.sender).await?,
|
||||
) {
|
||||
Ok(s) => {
|
||||
self.verifications
|
||||
.insert(e.content.transaction_id.clone(), s);
|
||||
|
@ -165,9 +189,19 @@ impl VerificationMachine {
|
|||
if let Some(s) = self.get_sas(&e.content.transaction_id) {
|
||||
self.receive_event_helper(&s, event);
|
||||
|
||||
if s.is_done() && !s.mark_device_as_verified().await? {
|
||||
if let Some(r) = s.cancel() {
|
||||
self.outgoing_to_device_messages.insert(r.txn_id.clone(), r);
|
||||
if s.is_done() {
|
||||
if !s.mark_device_as_verified().await? {
|
||||
if let Some(r) = s.cancel() {
|
||||
self.outgoing_to_device_messages.insert(
|
||||
r.0,
|
||||
OutgoingRequest {
|
||||
request_id: r.0,
|
||||
request: Arc::new(r.1.into()),
|
||||
},
|
||||
);
|
||||
}
|
||||
} else {
|
||||
s.mark_identity_as_verified().await?;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -194,6 +228,7 @@ mod test {
|
|||
|
||||
use super::{Sas, VerificationMachine};
|
||||
use crate::{
|
||||
requests::OutgoingRequests,
|
||||
store::memorystore::MemoryStore,
|
||||
verification::test::{get_content_from_request, wrap_any_to_device_content},
|
||||
Account, CryptoStore, ReadOnlyDevice,
|
||||
|
@ -231,7 +266,7 @@ mod test {
|
|||
.unwrap();
|
||||
|
||||
let machine = VerificationMachine::new(alice, Arc::new(Box::new(store)));
|
||||
let (bob_sas, start_content) = Sas::start(bob, alice_device, bob_store);
|
||||
let (bob_sas, start_content) = Sas::start(bob, alice_device, bob_store, None);
|
||||
machine
|
||||
.receive_event(&mut wrap_any_to_device_content(
|
||||
bob_sas.user_id(),
|
||||
|
@ -276,10 +311,15 @@ mod test {
|
|||
.next()
|
||||
.unwrap();
|
||||
|
||||
let txn_id = request.txn_id.clone();
|
||||
let txn_id = *request.request_id();
|
||||
|
||||
let mut event =
|
||||
wrap_any_to_device_content(alice.user_id(), get_content_from_request(&request));
|
||||
let r = if let OutgoingRequests::ToDeviceRequest(r) = request.request() {
|
||||
r
|
||||
} else {
|
||||
panic!("Invalid request type");
|
||||
};
|
||||
|
||||
let mut event = wrap_any_to_device_content(alice.user_id(), get_content_from_request(r));
|
||||
drop(request);
|
||||
alice_machine.mark_requests_as_sent(&txn_id);
|
||||
|
||||
|
|
|
@ -20,6 +20,7 @@ pub use sas::Sas;
|
|||
|
||||
#[cfg(test)]
|
||||
pub(crate) mod test {
|
||||
use crate::requests::{OutgoingRequest, OutgoingRequests};
|
||||
use serde_json::Value;
|
||||
|
||||
use matrix_sdk_common::{
|
||||
|
@ -36,6 +37,16 @@ pub(crate) mod test {
|
|||
wrap_any_to_device_content(sender, content)
|
||||
}
|
||||
|
||||
pub(crate) fn outgoing_request_to_event(
|
||||
sender: &UserId,
|
||||
request: &OutgoingRequest,
|
||||
) -> AnyToDeviceEvent {
|
||||
match request.request() {
|
||||
OutgoingRequests::ToDeviceRequest(r) => request_to_event(sender, r),
|
||||
_ => panic!("Unsupported outgoing request"),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn wrap_any_to_device_content(
|
||||
sender: &UserId,
|
||||
content: AnyToDeviceEventContent,
|
||||
|
|
|
@ -30,12 +30,13 @@ use matrix_sdk_common::{
|
|||
uuid::Uuid,
|
||||
};
|
||||
|
||||
use crate::{Account, ReadOnlyDevice};
|
||||
use crate::{user_identity::UserIdentities, Account, ReadOnlyDevice};
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct SasIds {
|
||||
pub account: Account,
|
||||
pub other_device: ReadOnlyDevice,
|
||||
pub other_identity: Option<UserIdentities>,
|
||||
}
|
||||
|
||||
/// Get a tuple of an emoji and a description of the emoji using a number.
|
||||
|
@ -158,8 +159,9 @@ pub fn receive_mac_event(
|
|||
ids: &SasIds,
|
||||
flow_id: &str,
|
||||
event: &ToDeviceEvent<MacEventContent>,
|
||||
) -> Result<(Vec<ReadOnlyDevice>, Vec<String>), CancelCode> {
|
||||
) -> Result<(Vec<ReadOnlyDevice>, Vec<UserIdentities>), CancelCode> {
|
||||
let mut verified_devices = Vec::new();
|
||||
let mut verified_identities = Vec::new();
|
||||
|
||||
let info = extra_mac_info_receive(&ids, flow_id);
|
||||
|
||||
|
@ -201,6 +203,25 @@ pub fn receive_mac_event(
|
|||
} else {
|
||||
return Err(CancelCode::KeyMismatch);
|
||||
}
|
||||
} else if let Some(identity) = &ids.other_identity {
|
||||
if let Some(key) = identity.master_key().get_key(&key_id) {
|
||||
// TODO we should check that the master key signs the device,
|
||||
// this way we know the master key also trusts the device
|
||||
if key_mac
|
||||
== &sas
|
||||
.calculate_mac(key, &format!("{}{}", info, key_id))
|
||||
.expect("Can't calculate SAS MAC")
|
||||
{
|
||||
trace!(
|
||||
"Successfully verified the master key {} from {}",
|
||||
key_id,
|
||||
event.sender
|
||||
);
|
||||
verified_identities.push(identity.clone())
|
||||
} else {
|
||||
return Err(CancelCode::KeyMismatch);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
warn!(
|
||||
"Key ID {} in MAC event from {} {} doesn't belong to any device \
|
||||
|
@ -210,10 +231,9 @@ pub fn receive_mac_event(
|
|||
ids.other_device.device_id()
|
||||
);
|
||||
}
|
||||
// TODO add an else if branch for the master key here
|
||||
}
|
||||
|
||||
Ok((verified_devices, vec![]))
|
||||
Ok((verified_devices, verified_identities))
|
||||
}
|
||||
|
||||
/// Get the extra info that will be used when we generate a MAC and need to send
|
||||
|
@ -444,7 +464,7 @@ pub fn content_to_request(
|
|||
recipient: &UserId,
|
||||
recipient_device: &DeviceId,
|
||||
content: AnyToDeviceEventContent,
|
||||
) -> OwnedToDeviceRequest {
|
||||
) -> (Uuid, OwnedToDeviceRequest) {
|
||||
let mut messages = BTreeMap::new();
|
||||
let mut user_messages = BTreeMap::new();
|
||||
|
||||
|
@ -463,11 +483,16 @@ pub fn content_to_request(
|
|||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
OwnedToDeviceRequest {
|
||||
txn_id: Uuid::new_v4().to_string(),
|
||||
event_type,
|
||||
messages,
|
||||
}
|
||||
let request_id = Uuid::new_v4();
|
||||
|
||||
(
|
||||
request_id,
|
||||
OwnedToDeviceRequest {
|
||||
txn_id: request_id.to_string(),
|
||||
event_type,
|
||||
messages,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
|
@ -31,9 +31,13 @@ use matrix_sdk_common::{
|
|||
AnyToDeviceEvent, AnyToDeviceEventContent, ToDeviceEvent,
|
||||
},
|
||||
identifiers::{DeviceId, UserId},
|
||||
uuid::Uuid,
|
||||
};
|
||||
|
||||
use crate::{Account, CryptoStore, CryptoStoreError, ReadOnlyDevice, TrustState};
|
||||
use crate::{
|
||||
user_identity::UserIdentities, Account, CryptoStore, CryptoStoreError, LocalTrust,
|
||||
ReadOnlyDevice,
|
||||
};
|
||||
|
||||
pub use helpers::content_to_request;
|
||||
use sas_state::{
|
||||
|
@ -47,6 +51,7 @@ pub struct Sas {
|
|||
store: Arc<Box<dyn CryptoStore>>,
|
||||
account: Account,
|
||||
other_device: ReadOnlyDevice,
|
||||
other_identity: Option<UserIdentities>,
|
||||
flow_id: Arc<String>,
|
||||
}
|
||||
|
||||
|
@ -101,8 +106,13 @@ impl Sas {
|
|||
account: Account,
|
||||
other_device: ReadOnlyDevice,
|
||||
store: Arc<Box<dyn CryptoStore>>,
|
||||
other_identity: Option<UserIdentities>,
|
||||
) -> (Sas, StartEventContent) {
|
||||
let (inner, content) = InnerSas::start(account.clone(), other_device.clone());
|
||||
let (inner, content) = InnerSas::start(
|
||||
account.clone(),
|
||||
other_device.clone(),
|
||||
other_identity.clone(),
|
||||
);
|
||||
let flow_id = inner.verification_flow_id();
|
||||
|
||||
let sas = Sas {
|
||||
|
@ -111,6 +121,7 @@ impl Sas {
|
|||
store,
|
||||
other_device,
|
||||
flow_id,
|
||||
other_identity,
|
||||
};
|
||||
|
||||
(sas, content)
|
||||
|
@ -131,13 +142,21 @@ impl Sas {
|
|||
other_device: ReadOnlyDevice,
|
||||
store: Arc<Box<dyn CryptoStore>>,
|
||||
event: &ToDeviceEvent<StartEventContent>,
|
||||
other_identity: Option<UserIdentities>,
|
||||
) -> Result<Sas, AnyToDeviceEventContent> {
|
||||
let inner = InnerSas::from_start_event(account.clone(), other_device.clone(), event)?;
|
||||
let inner = InnerSas::from_start_event(
|
||||
account.clone(),
|
||||
other_device.clone(),
|
||||
event,
|
||||
other_identity.clone(),
|
||||
)?;
|
||||
let flow_id = inner.verification_flow_id();
|
||||
|
||||
Ok(Sas {
|
||||
inner: Arc::new(Mutex::new(inner)),
|
||||
account,
|
||||
other_device,
|
||||
other_identity,
|
||||
store,
|
||||
flow_id,
|
||||
})
|
||||
|
@ -150,7 +169,7 @@ impl Sas {
|
|||
pub fn accept(&self) -> Option<OwnedToDeviceRequest> {
|
||||
self.inner.lock().unwrap().accept().map(|c| {
|
||||
let content = AnyToDeviceEventContent::KeyVerificationAccept(c);
|
||||
self.content_to_request(content)
|
||||
self.content_to_request(content).1
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -171,16 +190,81 @@ impl Sas {
|
|||
(content, guard.is_done())
|
||||
};
|
||||
|
||||
if done && !self.mark_device_as_verified().await? {
|
||||
return Ok(self.cancel());
|
||||
if done {
|
||||
// TODO move the logic that marks and stores the device into the
|
||||
// else branch and only after the identity was verified as well. We
|
||||
// dont' want to verify one without the other.
|
||||
if !self.mark_device_as_verified().await? {
|
||||
return Ok(self.cancel().map(|r| r.1));
|
||||
} else {
|
||||
self.mark_identity_as_verified().await?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(content.map(|c| {
|
||||
let content = AnyToDeviceEventContent::KeyVerificationMac(c);
|
||||
self.content_to_request(content)
|
||||
self.content_to_request(content).1
|
||||
}))
|
||||
}
|
||||
|
||||
pub(crate) async fn mark_identity_as_verified(&self) -> Result<bool, CryptoStoreError> {
|
||||
// If there wasn't an identity available during the verification flow
|
||||
// return early as there's nothing to do.
|
||||
if self.other_identity.is_none() {
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
let identity = self.store.get_user_identity(self.other_user_id()).await?;
|
||||
|
||||
if let Some(identity) = identity {
|
||||
if identity.master_key() == self.other_identity.as_ref().unwrap().master_key() {
|
||||
if self
|
||||
.verified_identities()
|
||||
.map_or(false, |i| i.contains(&identity))
|
||||
{
|
||||
trace!(
|
||||
"Marking user identity of {} as verified.",
|
||||
identity.user_id(),
|
||||
);
|
||||
|
||||
if let UserIdentities::Own(i) = &identity {
|
||||
i.mark_as_verified();
|
||||
self.store.save_user_identities(&[identity]).await?;
|
||||
}
|
||||
// TODO if we have the private part of the user signing
|
||||
// key we should sign and upload a signature for this
|
||||
// identity.
|
||||
|
||||
Ok(true)
|
||||
} else {
|
||||
info!(
|
||||
"The interactive verification process didn't contain a \
|
||||
MAC for the user identity of {} {:?}",
|
||||
identity.user_id(),
|
||||
self.verified_identities(),
|
||||
);
|
||||
|
||||
Ok(false)
|
||||
}
|
||||
} else {
|
||||
warn!(
|
||||
"The master keys of {} have changed while an interactive \
|
||||
verification was going on, not marking the identity as verified.",
|
||||
identity.user_id(),
|
||||
);
|
||||
|
||||
Ok(false)
|
||||
}
|
||||
} else {
|
||||
info!(
|
||||
"The identity for {} was deleted while an interactive \
|
||||
verification was going on.",
|
||||
self.other_user_id(),
|
||||
);
|
||||
Ok(false)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn mark_device_as_verified(&self) -> Result<bool, CryptoStoreError> {
|
||||
let device = self
|
||||
.store
|
||||
|
@ -199,8 +283,11 @@ impl Sas {
|
|||
device.device_id()
|
||||
);
|
||||
|
||||
device.set_trust_state(TrustState::Verified);
|
||||
device.set_trust_state(LocalTrust::Verified);
|
||||
self.store.save_devices(&[device]).await?;
|
||||
// TODO if this is a device from our own user and we have
|
||||
// the private part of the self signing key, we should sign
|
||||
// the device and upload the signature.
|
||||
|
||||
Ok(true)
|
||||
} else {
|
||||
|
@ -226,7 +313,7 @@ impl Sas {
|
|||
let device = self.other_device();
|
||||
|
||||
info!(
|
||||
"The device {} {} was deleted while a interactive \
|
||||
"The device {} {} was deleted while an interactive \
|
||||
verification was going on.",
|
||||
device.user_id(),
|
||||
device.device_id()
|
||||
|
@ -241,7 +328,7 @@ impl Sas {
|
|||
///
|
||||
/// Returns None if the `Sas` object is already in a canceled state,
|
||||
/// otherwise it returns a request that needs to be sent out.
|
||||
pub fn cancel(&self) -> Option<OwnedToDeviceRequest> {
|
||||
pub fn cancel(&self) -> Option<(Uuid, OwnedToDeviceRequest)> {
|
||||
let mut guard = self.inner.lock().unwrap();
|
||||
let sas: InnerSas = (*guard).clone();
|
||||
let (sas, content) = sas.cancel(CancelCode::User);
|
||||
|
@ -250,7 +337,7 @@ impl Sas {
|
|||
content.map(|c| self.content_to_request(c))
|
||||
}
|
||||
|
||||
pub(crate) fn cancel_if_timed_out(&self) -> Option<OwnedToDeviceRequest> {
|
||||
pub(crate) fn cancel_if_timed_out(&self) -> Option<(Uuid, OwnedToDeviceRequest)> {
|
||||
if self.is_canceled() || self.is_done() {
|
||||
None
|
||||
} else if self.timed_out() {
|
||||
|
@ -317,10 +404,14 @@ impl Sas {
|
|||
self.inner.lock().unwrap().verified_devices()
|
||||
}
|
||||
|
||||
pub(crate) fn verified_identities(&self) -> Option<Arc<Vec<UserIdentities>>> {
|
||||
self.inner.lock().unwrap().verified_identities()
|
||||
}
|
||||
|
||||
pub(crate) fn content_to_request(
|
||||
&self,
|
||||
content: AnyToDeviceEventContent,
|
||||
) -> OwnedToDeviceRequest {
|
||||
) -> (Uuid, OwnedToDeviceRequest) {
|
||||
content_to_request(self.other_user_id(), self.other_device_id(), content)
|
||||
}
|
||||
}
|
||||
|
@ -338,8 +429,12 @@ enum InnerSas {
|
|||
}
|
||||
|
||||
impl InnerSas {
|
||||
fn start(account: Account, other_device: ReadOnlyDevice) -> (InnerSas, StartEventContent) {
|
||||
let sas = SasState::<Created>::new(account, other_device);
|
||||
fn start(
|
||||
account: Account,
|
||||
other_device: ReadOnlyDevice,
|
||||
other_identity: Option<UserIdentities>,
|
||||
) -> (InnerSas, StartEventContent) {
|
||||
let sas = SasState::<Created>::new(account, other_device, other_identity);
|
||||
let content = sas.as_content();
|
||||
(InnerSas::Created(sas), content)
|
||||
}
|
||||
|
@ -348,8 +443,9 @@ impl InnerSas {
|
|||
account: Account,
|
||||
other_device: ReadOnlyDevice,
|
||||
event: &ToDeviceEvent<StartEventContent>,
|
||||
other_identity: Option<UserIdentities>,
|
||||
) -> Result<InnerSas, AnyToDeviceEventContent> {
|
||||
match SasState::<Started>::from_start_event(account, other_device, event) {
|
||||
match SasState::<Started>::from_start_event(account, other_device, event, other_identity) {
|
||||
Ok(s) => Ok(InnerSas::Started(s)),
|
||||
Err(s) => Err(s.as_content()),
|
||||
}
|
||||
|
@ -542,6 +638,14 @@ impl InnerSas {
|
|||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn verified_identities(&self) -> Option<Arc<Vec<UserIdentities>>> {
|
||||
if let InnerSas::Done(s) = self {
|
||||
Some(s.verified_identities())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
@ -591,12 +695,13 @@ mod test {
|
|||
let bob = Account::new(&bob_id(), &bob_device_id());
|
||||
let bob_device = ReadOnlyDevice::from_account(&bob).await;
|
||||
|
||||
let alice_sas = SasState::<Created>::new(alice.clone(), bob_device);
|
||||
let alice_sas = SasState::<Created>::new(alice.clone(), bob_device, None);
|
||||
|
||||
let start_content = alice_sas.as_content();
|
||||
let event = wrap_to_device_event(alice_sas.user_id(), start_content);
|
||||
|
||||
let bob_sas = SasState::<Started>::from_start_event(bob.clone(), alice_device, &event);
|
||||
let bob_sas =
|
||||
SasState::<Started>::from_start_event(bob.clone(), alice_device, &event, None);
|
||||
|
||||
(alice_sas, bob_sas.unwrap())
|
||||
}
|
||||
|
@ -683,10 +788,10 @@ mod test {
|
|||
.await
|
||||
.unwrap();
|
||||
|
||||
let (alice, content) = Sas::start(alice, bob_device, alice_store);
|
||||
let (alice, content) = Sas::start(alice, bob_device, alice_store, None);
|
||||
let event = wrap_to_device_event(alice.user_id(), content);
|
||||
|
||||
let bob = Sas::from_start_event(bob, alice_device, bob_store, &event).unwrap();
|
||||
let bob = Sas::from_start_event(bob, alice_device, bob_store, &event, None).unwrap();
|
||||
let mut event = wrap_any_to_device_content(
|
||||
bob.user_id(),
|
||||
get_content_from_request(&bob.accept().unwrap()),
|
||||
|
|
|
@ -43,7 +43,7 @@ use matrix_sdk_common::{
|
|||
|
||||
use super::helpers::{get_decimal, get_emoji, get_mac_content, receive_mac_event, SasIds};
|
||||
|
||||
use crate::{Account, ReadOnlyDevice};
|
||||
use crate::{user_identity::UserIdentities, Account, ReadOnlyDevice};
|
||||
|
||||
const KEY_AGREEMENT_PROTOCOLS: &[KeyAgreementProtocol] =
|
||||
&[KeyAgreementProtocol::Curve25519HkdfSha256];
|
||||
|
@ -207,7 +207,7 @@ pub struct MacReceived {
|
|||
we_started: bool,
|
||||
their_pubkey: String,
|
||||
verified_devices: Arc<Vec<ReadOnlyDevice>>,
|
||||
verified_master_keys: Arc<Vec<String>>,
|
||||
verified_master_keys: Arc<Vec<UserIdentities>>,
|
||||
}
|
||||
|
||||
/// The SAS state indicating that the verification finished successfully.
|
||||
|
@ -217,7 +217,7 @@ pub struct MacReceived {
|
|||
#[derive(Clone, Debug)]
|
||||
pub struct Done {
|
||||
verified_devices: Arc<Vec<ReadOnlyDevice>>,
|
||||
verified_master_keys: Arc<Vec<String>>,
|
||||
verified_master_keys: Arc<Vec<UserIdentities>>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
|
@ -286,7 +286,11 @@ impl SasState<Created> {
|
|||
/// * `account` - Our own account.
|
||||
///
|
||||
/// * `other_device` - The other device which we are going to verify.
|
||||
pub fn new(account: Account, other_device: ReadOnlyDevice) -> SasState<Created> {
|
||||
pub fn new(
|
||||
account: Account,
|
||||
other_device: ReadOnlyDevice,
|
||||
other_identity: Option<UserIdentities>,
|
||||
) -> SasState<Created> {
|
||||
let verification_flow_id = Uuid::new_v4().to_string();
|
||||
|
||||
SasState {
|
||||
|
@ -294,6 +298,7 @@ impl SasState<Created> {
|
|||
ids: SasIds {
|
||||
account,
|
||||
other_device,
|
||||
other_identity,
|
||||
},
|
||||
verification_flow_id: Arc::new(verification_flow_id),
|
||||
|
||||
|
@ -382,6 +387,7 @@ impl SasState<Started> {
|
|||
account: Account,
|
||||
other_device: ReadOnlyDevice,
|
||||
event: &ToDeviceEvent<StartEventContent>,
|
||||
other_identity: Option<UserIdentities>,
|
||||
) -> Result<SasState<Started>, SasState<Canceled>> {
|
||||
if let StartMethod::MSasV1(content) = &event.content.method {
|
||||
let sas = OlmSas::new();
|
||||
|
@ -397,6 +403,7 @@ impl SasState<Started> {
|
|||
ids: SasIds {
|
||||
account,
|
||||
other_device,
|
||||
other_identity,
|
||||
},
|
||||
|
||||
creation_time: Arc::new(Instant::now()),
|
||||
|
@ -438,6 +445,7 @@ impl SasState<Started> {
|
|||
ids: SasIds {
|
||||
account,
|
||||
other_device,
|
||||
other_identity,
|
||||
},
|
||||
|
||||
verification_flow_id: Arc::new(event.content.transaction_id.clone()),
|
||||
|
@ -783,6 +791,11 @@ impl SasState<Done> {
|
|||
pub fn verified_devices(&self) -> Arc<Vec<ReadOnlyDevice>> {
|
||||
self.state.verified_devices.clone()
|
||||
}
|
||||
|
||||
/// Get the list of verified identities.
|
||||
pub fn verified_identities(&self) -> Arc<Vec<UserIdentities>> {
|
||||
self.state.verified_master_keys.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl Canceled {
|
||||
|
@ -871,12 +884,13 @@ mod test {
|
|||
let bob = Account::new(&bob_id(), &bob_device_id());
|
||||
let bob_device = ReadOnlyDevice::from_account(&bob).await;
|
||||
|
||||
let alice_sas = SasState::<Created>::new(alice.clone(), bob_device);
|
||||
let alice_sas = SasState::<Created>::new(alice.clone(), bob_device, None);
|
||||
|
||||
let start_content = alice_sas.as_content();
|
||||
let event = wrap_to_device_event(alice_sas.user_id(), start_content);
|
||||
|
||||
let bob_sas = SasState::<Started>::from_start_event(bob.clone(), alice_device, &event);
|
||||
let bob_sas =
|
||||
SasState::<Started>::from_start_event(bob.clone(), alice_device, &event, None);
|
||||
|
||||
(alice_sas, bob_sas.unwrap())
|
||||
}
|
||||
|
@ -1027,7 +1041,7 @@ mod test {
|
|||
let bob = Account::new(&bob_id(), &bob_device_id());
|
||||
let bob_device = ReadOnlyDevice::from_account(&bob).await;
|
||||
|
||||
let alice_sas = SasState::<Created>::new(alice.clone(), bob_device);
|
||||
let alice_sas = SasState::<Created>::new(alice.clone(), bob_device, None);
|
||||
|
||||
let mut start_content = alice_sas.as_content();
|
||||
|
||||
|
@ -1039,7 +1053,7 @@ mod test {
|
|||
}
|
||||
|
||||
let event = wrap_to_device_event(alice_sas.user_id(), start_content);
|
||||
SasState::<Started>::from_start_event(bob.clone(), alice_device.clone(), &event)
|
||||
SasState::<Started>::from_start_event(bob.clone(), alice_device.clone(), &event, None)
|
||||
.expect_err("Didn't cancel on invalid MAC method");
|
||||
|
||||
let mut start_content = alice_sas.as_content();
|
||||
|
@ -1050,7 +1064,7 @@ mod test {
|
|||
});
|
||||
|
||||
let event = wrap_to_device_event(alice_sas.user_id(), start_content);
|
||||
SasState::<Started>::from_start_event(bob.clone(), alice_device, &event)
|
||||
SasState::<Started>::from_start_event(bob.clone(), alice_device, &event, None)
|
||||
.expect_err("Didn't cancel on unknown sas method");
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue