Merge branch 'crypto-improvements' into master

This commit is contained in:
Damir Jelić 2020-08-21 18:16:48 +02:00
commit 176181bdcf
29 changed files with 1508 additions and 581 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View 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
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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