Merge branch 'secret-sharing'

This commit is contained in:
Damir Jelić 2021-08-13 20:38:31 +02:00
commit 623408913c
32 changed files with 2412 additions and 865 deletions

View file

@ -2120,6 +2120,14 @@ impl Client {
.unwrap(); .unwrap();
} }
} }
OutgoingRequests::KeysClaim(request) => {
if let Ok(resp) = self.send(request.clone(), None).await {
self.base_client
.mark_request_as_sent(r.request_id(), &resp)
.await
.unwrap();
}
}
} }
} }
} }

View file

@ -135,29 +135,42 @@ pub enum SessionUnpicklingError {
SessionTimestampError, SessionTimestampError,
} }
/// Error type describin different errors that happen when we check or create
/// signatures for a Matrix JSON object.
#[derive(Error, Debug)] #[derive(Error, Debug)]
pub enum SignatureError { pub enum SignatureError {
#[error("the signature used a unsupported algorithm")] /// The signature was made using an unsupported algorithm.
#[error("the signature used an unsupported algorithm")]
UnsupportedAlgorithm, UnsupportedAlgorithm,
#[error("the key id of the signing key is invalid")] /// The ID of the signing key isn't a valid key ID.
#[error("the ID of the signing key is invalid")]
InvalidKeyId(#[from] IdentifierError), InvalidKeyId(#[from] IdentifierError),
/// The signing key that should create or check a signature is missing.
#[error("the signing key is missing from the object that signed the message")] #[error("the signing key is missing from the object that signed the message")]
MissingSigningKey, MissingSigningKey,
#[error("the user id of the signing differs from the subkey user id")] /// The user id of signing key differs from the user id that provided the
/// signature.
#[error("the user id of the signing key differs user id that provided the signature")]
UserIdMissmatch, UserIdMissmatch,
/// The provided JSON value that was signed and the signature should be
/// checked isn't a valid JSON object.
#[error("the provided JSON value isn't an object")] #[error("the provided JSON value isn't an object")]
NotAnObject, NotAnObject,
/// The provided JSON value that was signed and the signature should be
/// checked isn't a valid JSON object.
#[error("the provided JSON object doesn't contain a signatures field")] #[error("the provided JSON object doesn't contain a signatures field")]
NoSignatureFound, NoSignatureFound,
/// The signature couldn't be verified.
#[error("the signature didn't match the provided key")] #[error("the signature didn't match the provided key")]
VerificationError, VerificationError,
/// The signed object couldn't be deserialized.
#[error(transparent)] #[error(transparent)]
JsonError(#[from] SerdeError), JsonError(#[from] SerdeError),
} }

View file

@ -215,8 +215,8 @@ impl<'a, R: Read + 'a> AttachmentEncryptor<'a, R> {
let mut iv = Zeroizing::new([0u8; IV_SIZE]); let mut iv = Zeroizing::new([0u8; IV_SIZE]);
getrandom(&mut *key).expect("Can't generate randomness"); getrandom(&mut *key).expect("Can't generate randomness");
// Only populate the first 8 bits with randomness, the rest is 0 // Only populate the first 8 bytes with randomness, the rest is 0
// initialized. // initialized for the counter.
getrandom(&mut iv[0..8]).expect("Can't generate randomness"); getrandom(&mut iv[0..8]).expect("Can't generate randomness");
let web_key = JsonWebKey::from(JsonWebKeyInit { let web_key = JsonWebKey::from(JsonWebKeyInit {

View file

@ -0,0 +1,303 @@
// 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.
mod machine;
use std::sync::Arc;
use dashmap::{DashMap, DashSet};
pub(crate) use machine::GossipMachine;
use matrix_sdk_common::uuid::Uuid;
use ruma::{
events::{
room_key_request::{Action, RequestedKeyInfo, RoomKeyRequestToDeviceEventContent},
secret::request::{
RequestAction, RequestToDeviceEventContent as SecretRequestEventContent, SecretName,
},
AnyToDeviceEventContent, ToDeviceEvent,
},
to_device::DeviceIdOrAllDevices,
DeviceId, DeviceIdBox, UserId,
};
use serde::{Deserialize, Serialize};
use thiserror::Error;
use tracing::error;
use crate::{
requests::{OutgoingRequest, ToDeviceRequest},
Device,
};
/// An error describing why a key share request won't be honored.
#[derive(Debug, Clone, Error, PartialEq)]
pub enum KeyForwardDecision {
/// The key request is from a device that we don't own, we're only sharing
/// sessions that we know the requesting device already was supposed to get.
#[error("can't find an active outbound group session")]
MissingOutboundSession,
/// The key request is from a device that we don't own and the device wasn't
/// meant to receive the session in the original key share.
#[error("outbound session wasn't shared with the requesting device")]
OutboundSessionNotShared,
/// The key request is from a device we own, yet we don't trust it.
#[error("requesting device isn't trusted")]
UntrustedDevice,
/// The outbound session was shared with the device, but the device either
/// accidentally or maliciously changed their curve25519 sender key.
#[error("the device has changed their curve25519 sender key")]
ChangedSenderKey,
}
/// A struct describing an outgoing key request.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GossipRequest {
/// The user we requested the secret from
pub request_recipient: UserId,
/// The unique id of the secret request.
pub request_id: Uuid,
/// The info of the requested secret.
pub info: SecretInfo,
/// Has the request been sent out.
pub sent_out: bool,
}
/// An enum over the various secret request types we can have.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum SecretInfo {
// Info for the `m.room_key_request` variant
KeyRequest(RequestedKeyInfo),
// Info for the `m.secret.request` variant
SecretRequest(SecretName),
}
impl From<RequestedKeyInfo> for SecretInfo {
fn from(i: RequestedKeyInfo) -> Self {
Self::KeyRequest(i)
}
}
impl From<SecretName> for SecretInfo {
fn from(i: SecretName) -> Self {
Self::SecretRequest(i)
}
}
impl GossipRequest {
/// Create an ougoing secret request for the given secret.
pub(crate) fn from_secret_name(own_user_id: UserId, secret_name: SecretName) -> Self {
Self {
request_recipient: own_user_id,
request_id: Uuid::new_v4(),
info: secret_name.into(),
sent_out: false,
}
}
fn request_type(&self) -> &str {
match &self.info {
SecretInfo::KeyRequest(_) => "m.room_key_request",
SecretInfo::SecretRequest(s) => s.as_ref(),
}
}
fn to_request(&self, own_device_id: &DeviceId) -> OutgoingRequest {
let content = match &self.info {
SecretInfo::KeyRequest(r) => {
AnyToDeviceEventContent::RoomKeyRequest(RoomKeyRequestToDeviceEventContent::new(
Action::Request,
Some(r.clone()),
own_device_id.to_owned(),
self.request_id.to_string(),
))
}
SecretInfo::SecretRequest(s) => {
AnyToDeviceEventContent::SecretRequest(SecretRequestEventContent::new(
RequestAction::Request(s.clone()),
own_device_id.to_owned(),
self.request_id.to_string(),
))
}
};
let request = ToDeviceRequest::new_with_id(
&self.request_recipient,
DeviceIdOrAllDevices::AllDevices,
content,
self.request_id,
);
OutgoingRequest { request_id: request.txn_id, request: Arc::new(request.into()) }
}
fn to_cancellation(&self, own_device_id: &DeviceId) -> OutgoingRequest {
let content = match self.info {
SecretInfo::KeyRequest(_) => {
AnyToDeviceEventContent::RoomKeyRequest(RoomKeyRequestToDeviceEventContent::new(
Action::CancelRequest,
None,
own_device_id.to_owned(),
self.request_id.to_string(),
))
}
SecretInfo::SecretRequest(_) => {
AnyToDeviceEventContent::SecretRequest(SecretRequestEventContent::new(
RequestAction::RequestCancellation,
own_device_id.to_owned(),
self.request_id.to_string(),
))
}
};
let request = ToDeviceRequest::new(
&self.request_recipient,
DeviceIdOrAllDevices::AllDevices,
content,
);
OutgoingRequest { request_id: request.txn_id, request: Arc::new(request.into()) }
}
}
impl PartialEq for GossipRequest {
fn eq(&self, other: &Self) -> bool {
let is_info_equal = match (&self.info, &other.info) {
(SecretInfo::KeyRequest(first), SecretInfo::KeyRequest(second)) => {
first.algorithm == second.algorithm
&& first.room_id == second.room_id
&& first.session_id == second.session_id
}
(SecretInfo::SecretRequest(first), SecretInfo::SecretRequest(second)) => {
first == second
}
(SecretInfo::KeyRequest(_), SecretInfo::SecretRequest(_))
| (SecretInfo::SecretRequest(_), SecretInfo::KeyRequest(_)) => false,
};
self.request_id == other.request_id && is_info_equal
}
}
#[derive(Debug, Clone)]
enum RequestEvent {
KeyShare(ToDeviceEvent<RoomKeyRequestToDeviceEventContent>),
Secret(ToDeviceEvent<SecretRequestEventContent>),
}
impl From<ToDeviceEvent<SecretRequestEventContent>> for RequestEvent {
fn from(e: ToDeviceEvent<SecretRequestEventContent>) -> Self {
Self::Secret(e)
}
}
impl From<ToDeviceEvent<RoomKeyRequestToDeviceEventContent>> for RequestEvent {
fn from(e: ToDeviceEvent<RoomKeyRequestToDeviceEventContent>) -> Self {
Self::KeyShare(e)
}
}
impl RequestEvent {
fn to_request_info(&self) -> RequestInfo {
RequestInfo::new(
self.sender().to_owned(),
self.requesting_device_id().into(),
self.request_id().to_owned(),
)
}
fn sender(&self) -> &UserId {
match self {
RequestEvent::KeyShare(e) => &e.sender,
RequestEvent::Secret(e) => &e.sender,
}
}
fn requesting_device_id(&self) -> &DeviceId {
match self {
RequestEvent::KeyShare(e) => &e.content.requesting_device_id,
RequestEvent::Secret(e) => &e.content.requesting_device_id,
}
}
fn request_id(&self) -> &str {
match self {
RequestEvent::KeyShare(e) => &e.content.request_id,
RequestEvent::Secret(e) => &e.content.request_id,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
struct RequestInfo {
sender: UserId,
requesting_device_id: DeviceIdBox,
request_id: String,
}
impl RequestInfo {
fn new(sender: UserId, requesting_device_id: DeviceIdBox, request_id: String) -> Self {
Self { sender, requesting_device_id, request_id }
}
}
/// A queue where we store room key requests that we want to serve but the
/// device that requested the key doesn't share an Olm session with us.
#[derive(Debug, Clone)]
struct WaitQueue {
requests_waiting_for_session: Arc<DashMap<RequestInfo, RequestEvent>>,
requests_ids_waiting: Arc<DashMap<(UserId, DeviceIdBox), DashSet<String>>>,
}
impl WaitQueue {
fn new() -> Self {
Self {
requests_waiting_for_session: Arc::new(DashMap::new()),
requests_ids_waiting: Arc::new(DashMap::new()),
}
}
#[cfg(test)]
fn is_empty(&self) -> bool {
self.requests_ids_waiting.is_empty() && self.requests_waiting_for_session.is_empty()
}
fn insert(&self, device: &Device, event: RequestEvent) {
let request_id = event.request_id().to_owned();
let key = RequestInfo::new(
device.user_id().to_owned(),
device.device_id().into(),
request_id.clone(),
);
self.requests_waiting_for_session.insert(key, event);
let key = (device.user_id().to_owned(), device.device_id().into());
self.requests_ids_waiting.entry(key).or_insert_with(DashSet::new).insert(request_id);
}
fn remove(&self, user_id: &UserId, device_id: &DeviceId) -> Vec<(RequestInfo, RequestEvent)> {
self.requests_ids_waiting
.remove(&(user_id.to_owned(), device_id.into()))
.map(|(_, request_ids)| {
request_ids
.iter()
.filter_map(|id| {
let key =
RequestInfo::new(user_id.to_owned(), device_id.into(), id.to_owned());
self.requests_waiting_for_session.remove(&key)
})
.collect()
})
.unwrap_or_default()
}
}

View file

@ -25,6 +25,7 @@ use std::{
use atomic::Atomic; use atomic::Atomic;
use matrix_sdk_common::locks::Mutex; use matrix_sdk_common::locks::Mutex;
use ruma::{ use ruma::{
api::client::r0::keys::upload_signatures::Request as SignatureUploadRequest,
encryption::{DeviceKeys, SignedKey}, encryption::{DeviceKeys, SignedKey},
events::{ events::{
forwarded_room_key::ForwardedRoomKeyToDeviceEventContent, forwarded_room_key::ForwardedRoomKeyToDeviceEventContent,
@ -103,7 +104,6 @@ where
/// A device represents a E2EE capable client of an user. /// A device represents a E2EE capable client of an user.
pub struct Device { pub struct Device {
pub(crate) inner: ReadOnlyDevice, pub(crate) inner: ReadOnlyDevice,
pub(crate) private_identity: Arc<Mutex<PrivateCrossSigningIdentity>>,
pub(crate) verification_machine: VerificationMachine, pub(crate) verification_machine: VerificationMachine,
pub(crate) own_identity: Option<ReadOnlyOwnUserIdentity>, pub(crate) own_identity: Option<ReadOnlyOwnUserIdentity>,
pub(crate) device_owner_identity: Option<ReadOnlyUserIdentities>, pub(crate) device_owner_identity: Option<ReadOnlyUserIdentities>,
@ -204,6 +204,33 @@ impl Device {
self.inner.is_cross_signing_trusted(&self.own_identity, &self.device_owner_identity) self.inner.is_cross_signing_trusted(&self.own_identity, &self.device_owner_identity)
} }
/// Manually verify this device.
///
/// This method will attempt to sign the device using our private cross
/// signing key.
///
/// This method will always fail if the device belongs to someone else, we
/// can only sign our own devices.
///
/// It can also fail if we don't have the private part of our self-signing
/// key.
///
/// Returns a request that needs to be sent out for the device to be marked
/// as verified.
pub async fn verify(&self) -> Result<SignatureUploadRequest, SignatureError> {
if self.user_id() == self.verification_machine.own_user_id() {
Ok(self
.verification_machine
.private_identity
.lock()
.await
.sign_device(&self.inner)
.await?)
} else {
Err(SignatureError::UserIdMissmatch)
}
}
/// Set the local trust state of the device to the given 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 /// This won't affect any cross signing trust state, this only sets a flag
@ -232,7 +259,7 @@ impl Device {
&self, &self,
content: AnyToDeviceEventContent, content: AnyToDeviceEventContent,
) -> OlmResult<(Session, EncryptedToDeviceEventContent)> { ) -> OlmResult<(Session, EncryptedToDeviceEventContent)> {
self.inner.encrypt(&*self.verification_machine.store, content).await self.inner.encrypt(self.verification_machine.store.inner(), content).await
} }
/// Encrypt the given inbound group session as a forwarded room key for this /// Encrypt the given inbound group session as a forwarded room key for this
@ -280,7 +307,6 @@ impl UserDevices {
pub fn get(&self, device_id: &DeviceId) -> Option<Device> { pub fn get(&self, device_id: &DeviceId) -> Option<Device> {
self.inner.get(device_id).map(|d| Device { self.inner.get(device_id).map(|d| Device {
inner: d.clone(), inner: d.clone(),
private_identity: self.private_identity.clone(),
verification_machine: self.verification_machine.clone(), verification_machine: self.verification_machine.clone(),
own_identity: self.own_identity.clone(), own_identity: self.own_identity.clone(),
device_owner_identity: self.device_owner_identity.clone(), device_owner_identity: self.device_owner_identity.clone(),
@ -302,7 +328,6 @@ impl UserDevices {
pub fn devices(&self) -> impl Iterator<Item = Device> + '_ { pub fn devices(&self) -> impl Iterator<Item = Device> + '_ {
self.inner.values().map(move |d| Device { self.inner.values().map(move |d| Device {
inner: d.clone(), inner: d.clone(),
private_identity: self.private_identity.clone(),
verification_machine: self.verification_machine.clone(), verification_machine: self.verification_machine.clone(),
own_identity: self.own_identity.clone(), own_identity: self.own_identity.clone(),
device_owner_identity: self.device_owner_identity.clone(), device_owner_identity: self.device_owner_identity.clone(),
@ -539,21 +564,11 @@ impl ReadOnlyDevice {
) )
} }
pub(crate) fn as_signature_message(&self) -> Value {
json!({
"user_id": &*self.user_id,
"device_id": &*self.device_id,
"keys": &*self.keys,
"algorithms": &*self.algorithms,
"signatures": &*self.signatures,
})
}
pub(crate) fn verify_device_keys( pub(crate) fn verify_device_keys(
&self, &self,
device_keys: &DeviceKeys, device_keys: &DeviceKeys,
) -> Result<(), SignatureError> { ) -> Result<(), SignatureError> {
let mut device_keys = serde_json::to_value(device_keys).unwrap(); let mut device_keys = serde_json::to_value(device_keys)?;
self.is_signed_by_device(&mut device_keys) self.is_signed_by_device(&mut device_keys)
} }

View file

@ -24,7 +24,7 @@ use ruma::{
api::client::r0::keys::get_keys::Response as KeysQueryResponse, encryption::DeviceKeys, api::client::r0::keys::get_keys::Response as KeysQueryResponse, encryption::DeviceKeys,
DeviceId, DeviceIdBox, UserId, DeviceId, DeviceIdBox, UserId,
}; };
use tracing::{trace, warn}; use tracing::{info, trace, warn};
use crate::{ use crate::{
error::OlmResult, error::OlmResult,
@ -32,6 +32,7 @@ use crate::{
MasterPubkey, ReadOnlyDevice, ReadOnlyOwnUserIdentity, ReadOnlyUserIdentities, MasterPubkey, ReadOnlyDevice, ReadOnlyOwnUserIdentity, ReadOnlyUserIdentities,
ReadOnlyUserIdentity, SelfSigningPubkey, UserSigningPubkey, ReadOnlyUserIdentity, SelfSigningPubkey, UserSigningPubkey,
}, },
olm::PrivateCrossSigningIdentity,
requests::KeysQueryRequest, requests::KeysQueryRequest,
store::{Changes, DeviceChanges, IdentityChanges, Result as StoreResult, Store}, store::{Changes, DeviceChanges, IdentityChanges, Result as StoreResult, Store},
}; };
@ -75,11 +76,13 @@ impl IdentityManager {
) -> OlmResult<(DeviceChanges, IdentityChanges)> { ) -> OlmResult<(DeviceChanges, IdentityChanges)> {
let changed_devices = let changed_devices =
self.handle_devices_from_key_query(response.device_keys.clone()).await?; self.handle_devices_from_key_query(response.device_keys.clone()).await?;
let changed_identities = self.handle_cross_singing_keys(response).await?; let (changed_identities, cross_signing_identity) =
self.handle_cross_singing_keys(response).await?;
let changes = Changes { let changes = Changes {
identities: changed_identities.clone(), identities: changed_identities.clone(),
devices: changed_devices.clone(), devices: changed_devices.clone(),
private_identity: cross_signing_identity,
..Default::default() ..Default::default()
}; };
@ -137,18 +140,20 @@ impl IdentityManager {
user_id: UserId, user_id: UserId,
device_map: BTreeMap<DeviceIdBox, DeviceKeys>, device_map: BTreeMap<DeviceIdBox, DeviceKeys>,
) -> StoreResult<DeviceChanges> { ) -> StoreResult<DeviceChanges> {
let own_device_id = (&*own_device_id).to_owned();
let mut changes = DeviceChanges::default(); let mut changes = DeviceChanges::default();
let current_devices: HashSet<DeviceIdBox> = device_map.keys().cloned().collect(); let current_devices: HashSet<DeviceIdBox> = device_map.keys().cloned().collect();
let tasks = device_map.into_iter().filter_map(|(device_id, device_keys)| { let tasks = device_map.into_iter().filter_map(|(device_id, device_keys)| {
// We don't need our own device in the device store. if user_id != device_keys.user_id || device_id != device_keys.device_id {
if user_id == *own_user_id && *device_id == *own_device_id {
None
} else if user_id != device_keys.user_id || device_id != device_keys.device_id {
warn!( warn!(
"Mismatch in device keys payload of device {}|{} from user {}|{}", user_id = user_id.as_str(),
device_id, device_keys.device_id, user_id, device_keys.user_id device_id = device_id.as_str(),
device_key_user = device_keys.user_id.as_str(),
device_key_device_id = device_keys.device_id.as_str(),
"Mismatch in the device keys payload",
); );
None None
} else { } else {
@ -169,13 +174,14 @@ impl IdentityManager {
} }
let current_devices: HashSet<&DeviceIdBox> = current_devices.iter().collect(); let current_devices: HashSet<&DeviceIdBox> = current_devices.iter().collect();
let stored_devices = store.get_readonly_devices(&user_id).await?; let stored_devices = store.get_readonly_devices_unfiltered(&user_id).await?;
let stored_devices_set: HashSet<&DeviceIdBox> = stored_devices.keys().collect(); let stored_devices_set: HashSet<&DeviceIdBox> = stored_devices.keys().collect();
let deleted_devices_set = stored_devices_set.difference(&current_devices); let deleted_devices_set = stored_devices_set.difference(&current_devices);
for device_id in deleted_devices_set { for device_id in deleted_devices_set {
if let Some(device) = stored_devices.get(*device_id) { if user_id == *own_user_id && *device_id == &own_device_id {
warn!("Our own device has been deleted");
} else if let Some(device) = stored_devices.get(*device_id) {
device.mark_as_deleted(); device.mark_as_deleted();
changes.deleted.push(device.clone()); changes.deleted.push(device.clone());
} }
@ -231,8 +237,9 @@ impl IdentityManager {
async fn handle_cross_singing_keys( async fn handle_cross_singing_keys(
&self, &self,
response: &KeysQueryResponse, response: &KeysQueryResponse,
) -> StoreResult<IdentityChanges> { ) -> StoreResult<(IdentityChanges, Option<PrivateCrossSigningIdentity>)> {
let mut changes = IdentityChanges::default(); let mut changes = IdentityChanges::default();
let mut changed_identity = None;
for (user_id, master_key) in &response.master_keys { for (user_id, master_key) in &response.master_keys {
let master_key = MasterPubkey::from(master_key); let master_key = MasterPubkey::from(master_key);
@ -240,7 +247,10 @@ impl IdentityManager {
let self_signing = if let Some(s) = response.self_signing_keys.get(user_id) { let self_signing = if let Some(s) = response.self_signing_keys.get(user_id) {
SelfSigningPubkey::from(s) SelfSigningPubkey::from(s)
} else { } else {
warn!("User identity for user {} didn't contain a self signing pubkey", user_id); warn!(
user_id = user_id.as_str(),
"A user identity didn't contain a self signing pubkey"
);
continue; continue;
}; };
@ -252,9 +262,9 @@ impl IdentityManager {
UserSigningPubkey::from(s) UserSigningPubkey::from(s)
} else { } else {
warn!( warn!(
"User identity for our own user {} didn't \ user_id = user_id.as_str(),
contain a user signing pubkey", "User identity for our own user didn't \
user_id contain a user signing pubkey",
); );
continue; continue;
}; };
@ -274,8 +284,8 @@ impl IdentityManager {
|| user_signing.user_id() != user_id || user_signing.user_id() != user_id
{ {
warn!( warn!(
"User id mismatch in one of the cross signing keys for user {}", user_id = user_id.as_str(),
user_id "User ID mismatch in one of the cross signing keys",
); );
continue; continue;
} }
@ -284,14 +294,14 @@ impl IdentityManager {
.map(|i| (ReadOnlyUserIdentities::Own(i), true)) .map(|i| (ReadOnlyUserIdentities::Own(i), true))
} else { } else {
warn!( warn!(
"User identity for our own user {} didn't contain a \ user_id = user_id.as_str(),
"User identity for our own user didn't contain a \
user signing pubkey", user signing pubkey",
user_id
); );
continue; continue;
} }
} else if master_key.user_id() != user_id || self_signing.user_id() != user_id { } 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); warn!(user = user_id.as_str(), "User ID mismatch in one of the cross signing keys",);
continue; continue;
} else { } else {
ReadOnlyUserIdentity::new(master_key, self_signing) ReadOnlyUserIdentity::new(master_key, self_signing)
@ -300,21 +310,38 @@ impl IdentityManager {
match result { match result {
Ok((i, new)) => { Ok((i, new)) => {
trace!("Updated or created new user identity for {}: {:?}", user_id, i); if let Some(identity) = i.own() {
let private_identity = self.store.private_identity();
let private_identity = private_identity.lock().await;
let result = private_identity.clear_if_differs(identity).await;
if result.any_cleared() {
changed_identity = Some((&*private_identity).clone());
info!(cleared =? result, "Removed some or all of our private cross signing keys");
}
}
if new { if new {
trace!(user_id = user_id.as_str(), identity =? i, "Created new user identity");
changes.new.push(i); changes.new.push(i);
} else { } else {
trace!(user_id = user_id.as_str(), identity =? i, "Updated a user identity");
changes.changed.push(i); changes.changed.push(i);
} }
} }
Err(e) => { Err(e) => {
warn!("Couldn't update or create new user identity for {}: {:?}", user_id, e); warn!(
user_id = user_id.as_str(),
error =? e,
"Couldn't update or create new user identity for"
);
continue; continue;
} }
} }
} }
Ok(changes) Ok((changes, changed_identity))
} }
/// Get a key query request if one is needed. /// Get a key query request if one is needed.

View file

@ -23,7 +23,8 @@ use std::{
}; };
use ruma::{ use ruma::{
encryption::{CrossSigningKey, KeyUsage}, api::client::r0::keys::upload_signatures::Request as SignatureUploadRequest,
encryption::{CrossSigningKey, DeviceKeys, KeyUsage},
events::{ events::{
key::verification::VerificationMethod, room::message::KeyVerificationRequestEventContent, key::verification::VerificationMethod, room::message::KeyVerificationRequestEventContent,
}, },
@ -31,13 +32,15 @@ use ruma::{
}; };
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_json::to_value; use serde_json::to_value;
use tracing::error;
use super::{atomic_bool_deserializer, atomic_bool_serializer}; use super::{atomic_bool_deserializer, atomic_bool_serializer};
#[cfg(test)]
use crate::olm::PrivateCrossSigningIdentity;
use crate::{ use crate::{
error::SignatureError, olm::Utility, verification::VerificationMachine, CryptoStoreError, error::SignatureError,
OutgoingVerificationRequest, ReadOnlyDevice, VerificationRequest, olm::Utility,
store::{Changes, IdentityChanges},
verification::VerificationMachine,
CryptoStoreError, OutgoingVerificationRequest, ReadOnlyDevice, VerificationRequest,
}; };
/// Enum over the different user identity types we can have. /// Enum over the different user identity types we can have.
@ -104,6 +107,27 @@ impl Deref for OwnUserIdentity {
} }
impl OwnUserIdentity { impl OwnUserIdentity {
/// Mark our user identity as verified.
///
/// This will mark the identity locally as verified and sign it with our own
/// device.
///
/// Returns a signature upload request that needs to be sent out.
pub async fn verify(&self) -> Result<SignatureUploadRequest, SignatureError> {
self.mark_as_verified();
let changes = Changes {
identities: IdentityChanges { changed: vec![self.inner.clone().into()], new: vec![] },
..Default::default()
};
if let Err(e) = self.verification_machine.store.save_changes(changes).await {
error!(error =? e, "Couldn't store our own user identity after marking it as verified");
}
self.verification_machine.store.account.sign_master_key(self.master_key.clone()).await
}
/// Send a verification request to our other devices. /// Send a verification request to our other devices.
pub async fn request_verification( pub async fn request_verification(
&self, &self,
@ -124,6 +148,19 @@ impl OwnUserIdentity {
self.request_verification_helper(Some(methods)).await self.request_verification_helper(Some(methods)).await
} }
/// Does our user identity trust our own device, i.e. have we signed our
/// own device keys with our self-signing key.
pub async fn trusts_our_own_device(&self) -> Result<bool, CryptoStoreError> {
Ok(if let Some(signatures) = self.verification_machine.store.device_signatures().await? {
let mut device_keys = self.verification_machine.store.account.device_keys().await;
device_keys.signatures = signatures;
self.inner.self_signing_key().verify_device_keys(device_keys).is_ok()
} else {
false
})
}
async fn request_verification_helper( async fn request_verification_helper(
&self, &self,
methods: Option<Vec<VerificationMethod>>, methods: Option<Vec<VerificationMethod>>,
@ -156,6 +193,7 @@ impl OwnUserIdentity {
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct UserIdentity { pub struct UserIdentity {
pub(crate) inner: ReadOnlyUserIdentity, pub(crate) inner: ReadOnlyUserIdentity,
pub(crate) own_identity: Option<ReadOnlyOwnUserIdentity>,
pub(crate) verification_machine: VerificationMachine, pub(crate) verification_machine: VerificationMachine,
} }
@ -168,6 +206,38 @@ impl Deref for UserIdentity {
} }
impl UserIdentity { impl UserIdentity {
/// Is this user identity verified.
pub fn verified(&self) -> bool {
self.own_identity
.as_ref()
.map(|o| o.is_identity_signed(&self.inner).is_ok())
.unwrap_or(false)
}
/// Manually verify this user.
///
/// This method will attempt to sign the user identity using our private
/// cross signing key.
///
/// This method fails if we don't have the private part of our user-signing
/// key.
///
/// Returns a request that needs to be sent out for the user to be marked
/// as verified.
pub async fn verify(&self) -> Result<SignatureUploadRequest, SignatureError> {
if self.user_id() == self.verification_machine.own_user_id() {
Ok(self
.verification_machine
.private_identity
.lock()
.await
.sign_user(&self.inner)
.await?)
} else {
Err(SignatureError::UserIdMissmatch)
}
}
/// Create a `VerificationRequest` object after the verification request /// Create a `VerificationRequest` object after the verification request
/// content has been sent out. /// content has been sent out.
pub async fn request_verification( pub async fn request_verification(
@ -494,6 +564,22 @@ impl SelfSigningPubkey {
&self.0.keys &self.0.keys
} }
pub(crate) fn verify_device_keys(&self, device_keys: DeviceKeys) -> Result<(), SignatureError> {
let (key_id, key) = self.0.keys.iter().next().ok_or(SignatureError::MissingSigningKey)?;
// TODO check that the usage is OK.
let mut device = to_value(device_keys)?;
let utility = Utility::new();
utility.verify_json(
&self.0.user_id,
&DeviceKeyId::try_from(key_id.as_str())?,
key,
&mut device,
)
}
/// Check if the given device is signed by this self signing key. /// Check if the given device is signed by this self signing key.
/// ///
/// # Arguments /// # Arguments
@ -503,17 +589,7 @@ impl SelfSigningPubkey {
/// Returns an empty result if the signature check succeeded, otherwise a /// Returns an empty result if the signature check succeeded, otherwise a
/// SignatureError indicating why the check failed. /// SignatureError indicating why the check failed.
pub(crate) fn verify_device(&self, device: &ReadOnlyDevice) -> Result<(), SignatureError> { pub(crate) fn verify_device(&self, device: &ReadOnlyDevice) -> Result<(), SignatureError> {
let (key_id, key) = self.0.keys.iter().next().ok_or(SignatureError::MissingSigningKey)?; self.verify_device_keys(device.as_device_keys())
// TODO check that the usage is OK.
let utility = Utility::new();
utility.verify_json(
&self.0.user_id,
&DeviceKeyId::try_from(key_id.as_str())?,
key,
&mut device.as_signature_message(),
)
} }
} }
@ -590,6 +666,13 @@ impl ReadOnlyUserIdentities {
} }
} }
pub(crate) fn into_own(self) -> Option<ReadOnlyOwnUserIdentity> {
match self {
ReadOnlyUserIdentities::Own(i) => Some(i),
_ => None,
}
}
/// Destructure the enum into an `UserIdentity` if it's of the correct /// Destructure the enum into an `UserIdentity` if it's of the correct
/// type. /// type.
pub fn other(&self) -> Option<&ReadOnlyUserIdentity> { pub fn other(&self) -> Option<&ReadOnlyUserIdentity> {
@ -629,7 +712,7 @@ impl ReadOnlyUserIdentity {
/// ///
/// Returns a `SignatureError` if the self signing key fails to be correctly /// Returns a `SignatureError` if the self signing key fails to be correctly
/// verified by the given master key. /// verified by the given master key.
pub fn new( pub(crate) fn new(
master_key: MasterPubkey, master_key: MasterPubkey,
self_signing_key: SelfSigningPubkey, self_signing_key: SelfSigningPubkey,
) -> Result<Self, SignatureError> { ) -> Result<Self, SignatureError> {
@ -639,7 +722,7 @@ impl ReadOnlyUserIdentity {
} }
#[cfg(test)] #[cfg(test)]
pub async fn from_private(identity: &PrivateCrossSigningIdentity) -> Self { pub(crate) async fn from_private(identity: &crate::olm::PrivateCrossSigningIdentity) -> Self {
let master_key = identity.master_key.lock().await.as_ref().unwrap().public_key.clone(); let master_key = identity.master_key.lock().await.as_ref().unwrap().public_key.clone();
let self_signing_key = let self_signing_key =
identity.self_signing_key.lock().await.as_ref().unwrap().public_key.clone(); identity.self_signing_key.lock().await.as_ref().unwrap().public_key.clone();
@ -671,7 +754,7 @@ impl ReadOnlyUserIdentity {
/// * `self_signing_key` - The new self signing key of user identity. /// * `self_signing_key` - The new self signing key of user identity.
/// ///
/// Returns a `SignatureError` if we failed to update the identity. /// Returns a `SignatureError` if we failed to update the identity.
pub fn update( pub(crate) fn update(
&mut self, &mut self,
master_key: MasterPubkey, master_key: MasterPubkey,
self_signing_key: SelfSigningPubkey, self_signing_key: SelfSigningPubkey,
@ -696,7 +779,7 @@ impl ReadOnlyUserIdentity {
/// ///
/// Returns an empty result if the signature check succeeded, otherwise a /// Returns an empty result if the signature check succeeded, otherwise a
/// SignatureError indicating why the check failed. /// SignatureError indicating why the check failed.
pub fn is_device_signed(&self, device: &ReadOnlyDevice) -> Result<(), SignatureError> { pub(crate) fn is_device_signed(&self, device: &ReadOnlyDevice) -> Result<(), SignatureError> {
if self.user_id() != device.user_id() { if self.user_id() != device.user_id() {
return Err(SignatureError::UserIdMissmatch); return Err(SignatureError::UserIdMissmatch);
} }
@ -739,7 +822,7 @@ impl ReadOnlyOwnUserIdentity {
/// ///
/// Returns a `SignatureError` if the self signing key fails to be correctly /// Returns a `SignatureError` if the self signing key fails to be correctly
/// verified by the given master key. /// verified by the given master key.
pub fn new( pub(crate) fn new(
master_key: MasterPubkey, master_key: MasterPubkey,
self_signing_key: SelfSigningPubkey, self_signing_key: SelfSigningPubkey,
user_signing_key: UserSigningPubkey, user_signing_key: UserSigningPubkey,
@ -785,7 +868,7 @@ impl ReadOnlyOwnUserIdentity {
/// ///
/// Returns an empty result if the signature check succeeded, otherwise a /// Returns an empty result if the signature check succeeded, otherwise a
/// SignatureError indicating why the check failed. /// SignatureError indicating why the check failed.
pub fn is_identity_signed( pub(crate) fn is_identity_signed(
&self, &self,
identity: &ReadOnlyUserIdentity, identity: &ReadOnlyUserIdentity,
) -> Result<(), SignatureError> { ) -> Result<(), SignatureError> {
@ -804,7 +887,7 @@ impl ReadOnlyOwnUserIdentity {
/// ///
/// Returns an empty result if the signature check succeeded, otherwise a /// Returns an empty result if the signature check succeeded, otherwise a
/// SignatureError indicating why the check failed. /// SignatureError indicating why the check failed.
pub fn is_device_signed(&self, device: &ReadOnlyDevice) -> Result<(), SignatureError> { pub(crate) fn is_device_signed(&self, device: &ReadOnlyDevice) -> Result<(), SignatureError> {
if self.user_id() != device.user_id() { if self.user_id() != device.user_id() {
return Err(SignatureError::UserIdMissmatch); return Err(SignatureError::UserIdMissmatch);
} }
@ -835,7 +918,7 @@ impl ReadOnlyOwnUserIdentity {
/// * `user_signing_key` - The new user 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. /// Returns a `SignatureError` if we failed to update the identity.
pub fn update( pub(crate) fn update(
&mut self, &mut self,
master_key: MasterPubkey, master_key: MasterPubkey,
self_signing_key: SelfSigningPubkey, self_signing_key: SelfSigningPubkey,
@ -939,14 +1022,13 @@ pub(crate) mod test {
Arc::new(Mutex::new(PrivateCrossSigningIdentity::empty(second.user_id().clone()))); Arc::new(Mutex::new(PrivateCrossSigningIdentity::empty(second.user_id().clone())));
let verification_machine = VerificationMachine::new( let verification_machine = VerificationMachine::new(
ReadOnlyAccount::new(second.user_id(), second.device_id()), ReadOnlyAccount::new(second.user_id(), second.device_id()),
private_identity.clone(), private_identity,
Arc::new(MemoryStore::new()), Arc::new(MemoryStore::new()),
); );
let first = Device { let first = Device {
inner: first, inner: first,
verification_machine: verification_machine.clone(), verification_machine: verification_machine.clone(),
private_identity: private_identity.clone(),
own_identity: Some(identity.clone()), own_identity: Some(identity.clone()),
device_owner_identity: Some(ReadOnlyUserIdentities::Own(identity.clone())), device_owner_identity: Some(ReadOnlyUserIdentities::Own(identity.clone())),
}; };
@ -954,7 +1036,6 @@ pub(crate) mod test {
let second = Device { let second = Device {
inner: second, inner: second,
verification_machine, verification_machine,
private_identity,
own_identity: Some(identity.clone()), own_identity: Some(identity.clone()),
device_owner_identity: Some(ReadOnlyUserIdentities::Own(identity.clone())), device_owner_identity: Some(ReadOnlyUserIdentities::Own(identity.clone())),
}; };
@ -991,7 +1072,6 @@ pub(crate) mod test {
let mut device = Device { let mut device = Device {
inner: device, inner: device,
verification_machine: verification_machine.clone(), verification_machine: verification_machine.clone(),
private_identity: id.clone(),
own_identity: Some(public_identity.clone()), own_identity: Some(public_identity.clone()),
device_owner_identity: Some(public_identity.clone().into()), device_owner_identity: Some(public_identity.clone().into()),
}; };

View file

@ -29,8 +29,8 @@
mod error; mod error;
mod file_encryption; mod file_encryption;
mod gossiping;
mod identities; mod identities;
mod key_request;
mod machine; mod machine;
pub mod olm; pub mod olm;
mod requests; mod requests;
@ -39,7 +39,7 @@ pub mod store;
mod utilities; mod utilities;
mod verification; mod verification;
pub use error::{MegolmError, OlmError}; pub use error::{MegolmError, OlmError, SignatureError};
pub use file_encryption::{ pub use file_encryption::{
decrypt_key_export, encrypt_key_export, AttachmentDecryptor, AttachmentEncryptor, decrypt_key_export, encrypt_key_export, AttachmentDecryptor, AttachmentEncryptor,
DecryptorError, EncryptionInfo, KeyExportError, DecryptorError, EncryptionInfo, KeyExportError,
@ -50,13 +50,13 @@ pub use identities::{
}; };
pub use machine::OlmMachine; pub use machine::OlmMachine;
pub use matrix_qrcode; pub use matrix_qrcode;
pub use olm::EncryptionSettings;
pub(crate) use olm::ReadOnlyAccount; pub(crate) use olm::ReadOnlyAccount;
pub use olm::{CrossSigningStatus, EncryptionSettings};
pub use requests::{ pub use requests::{
IncomingResponse, KeysQueryRequest, OutgoingRequest, OutgoingRequests, IncomingResponse, KeysQueryRequest, OutgoingRequest, OutgoingRequests,
OutgoingVerificationRequest, RoomMessageRequest, ToDeviceRequest, OutgoingVerificationRequest, RoomMessageRequest, ToDeviceRequest, UploadSigningKeysRequest,
}; };
pub use store::CryptoStoreError; pub use store::{CrossSigningKeyExport, CryptoStoreError, SecretImportError};
pub use verification::{ pub use verification::{
AcceptSettings, CancelInfo, QrVerification, Sas, Verification, VerificationRequest, AcceptSettings, CancelInfo, QrVerification, Sas, Verification, VerificationRequest,
}; };

View file

@ -38,6 +38,7 @@ use ruma::{
EncryptedEventContent, EncryptedEventScheme, EncryptedToDeviceEventContent, EncryptedEventContent, EncryptedEventScheme, EncryptedToDeviceEventContent,
}, },
room_key::RoomKeyToDeviceEventContent, room_key::RoomKeyToDeviceEventContent,
secret::request::SecretName,
AnyMessageEventContent, AnyRoomEvent, AnyToDeviceEvent, SyncMessageEvent, ToDeviceEvent, AnyMessageEventContent, AnyRoomEvent, AnyToDeviceEvent, SyncMessageEvent, ToDeviceEvent,
}, },
DeviceId, DeviceIdBox, DeviceKeyAlgorithm, EventEncryptionAlgorithm, RoomId, UInt, UserId, DeviceId, DeviceIdBox, DeviceKeyAlgorithm, EventEncryptionAlgorithm, RoomId, UInt, UserId,
@ -48,21 +49,21 @@ use tracing::{debug, error, info, trace, warn};
use crate::store::sled::SledStore; use crate::store::sled::SledStore;
use crate::{ use crate::{
error::{EventError, MegolmError, MegolmResult, OlmError, OlmResult}, error::{EventError, MegolmError, MegolmResult, OlmError, OlmResult},
gossiping::GossipMachine,
identities::{user::UserIdentities, Device, IdentityManager, UserDevices}, identities::{user::UserIdentities, Device, IdentityManager, UserDevices},
key_request::KeyRequestMachine,
olm::{ olm::{
Account, EncryptionSettings, ExportedRoomKey, GroupSessionKey, IdentityKeys, Account, CrossSigningStatus, EncryptionSettings, ExportedRoomKey, GroupSessionKey,
InboundGroupSession, OlmDecryptionInfo, PrivateCrossSigningIdentity, ReadOnlyAccount, IdentityKeys, InboundGroupSession, OlmDecryptionInfo, PrivateCrossSigningIdentity,
SessionType, ReadOnlyAccount, SessionType,
}, },
requests::{IncomingResponse, OutgoingRequest, UploadSigningKeysRequest}, requests::{IncomingResponse, OutgoingRequest, UploadSigningKeysRequest},
session_manager::{GroupSessionManager, SessionManager}, session_manager::{GroupSessionManager, SessionManager},
store::{ store::{
Changes, CryptoStore, DeviceChanges, IdentityChanges, MemoryStore, Result as StoreResult, Changes, CryptoStore, DeviceChanges, IdentityChanges, MemoryStore, Result as StoreResult,
Store, SecretImportError, Store,
}, },
verification::{Verification, VerificationMachine, VerificationRequest}, verification::{Verification, VerificationMachine, VerificationRequest},
ToDeviceRequest, CrossSigningKeyExport, ToDeviceRequest,
}; };
/// State machine implementation of the Olm/Megolm encryption protocol used for /// State machine implementation of the Olm/Megolm encryption protocol used for
@ -93,7 +94,7 @@ pub struct OlmMachine {
verification_machine: VerificationMachine, verification_machine: VerificationMachine,
/// The state machine that is responsible to handle outgoing and incoming /// The state machine that is responsible to handle outgoing and incoming
/// key requests. /// key requests.
key_request_machine: KeyRequestMachine, key_request_machine: GossipMachine,
/// State machine handling public user identities and devices, keeping track /// State machine handling public user identities and devices, keeping track
/// of when a key query needs to be done and handling one. /// of when a key query needs to be done and handling one.
identity_manager: IdentityManager, identity_manager: IdentityManager,
@ -157,7 +158,7 @@ impl OlmMachine {
let group_session_manager = GroupSessionManager::new(account.clone(), store.clone()); let group_session_manager = GroupSessionManager::new(account.clone(), store.clone());
let key_request_machine = KeyRequestMachine::new( let key_request_machine = GossipMachine::new(
user_id.clone(), user_id.clone(),
device_id.clone(), device_id.clone(),
store.clone(), store.clone(),
@ -215,12 +216,15 @@ impl OlmMachine {
) -> StoreResult<Self> { ) -> StoreResult<Self> {
let account = match store.load_account().await? { let account = match store.load_account().await? {
Some(a) => { Some(a) => {
debug!("Restored account"); debug!(ed25519_key = a.identity_keys().ed25519(), "Restored an Olm account");
a a
} }
None => { None => {
debug!("Creating a new account");
let account = ReadOnlyAccount::new(&user_id, &device_id); let account = ReadOnlyAccount::new(&user_id, &device_id);
debug!(
ed25519_key = account.identity_keys().ed25519(),
"Created a new Olm account"
);
store.save_account(account.clone()).await?; store.save_account(account.clone()).await?;
account account
} }
@ -228,7 +232,14 @@ impl OlmMachine {
let identity = match store.load_identity().await? { let identity = match store.load_identity().await? {
Some(i) => { Some(i) => {
debug!("Restored the cross signing identity"); let master_key = i
.master_public_key()
.await
.and_then(|m| m.get_first_key().map(|m| m.to_string()));
debug!(
master_key =? master_key,
"Restored the cross signing identity"
);
i i
} }
None => { None => {
@ -277,6 +288,11 @@ impl OlmMachine {
self.account.identity_keys() self.account.identity_keys()
} }
/// Get the display name of our own device
pub async fn dislpay_name(&self) -> StoreResult<Option<String>> {
self.store.device_display_name().await
}
/// Get the outgoing requests that need to be sent out. /// Get the outgoing requests that need to be sent out.
/// ///
/// This returns a list of `OutGoingRequest`, those requests need to be sent /// This returns a list of `OutGoingRequest`, those requests need to be sent
@ -696,8 +712,12 @@ impl OlmMachine {
.key_request_machine .key_request_machine
.receive_forwarded_room_key(&decrypted.sender_key, &mut e) .receive_forwarded_room_key(&decrypted.sender_key, &mut e)
.await?), .await?),
AnyToDeviceEvent::SecretSend(mut e) => Ok((
self.key_request_machine.receive_secret(&decrypted.sender_key, &mut e).await?,
None,
)),
_ => { _ => {
warn!("Received an unexpected encrypted to-device event"); warn!(event_type =? event.event_type(), "Received an unexpected encrypted to-device event");
Ok((Some(event), None)) Ok((Some(event), None))
} }
} }
@ -747,6 +767,9 @@ impl OlmMachine {
AnyToDeviceEvent::RoomKeyRequest(e) => { AnyToDeviceEvent::RoomKeyRequest(e) => {
self.key_request_machine.receive_incoming_key_request(e) self.key_request_machine.receive_incoming_key_request(e)
} }
AnyToDeviceEvent::SecretRequest(e) => {
self.key_request_machine.receive_incoming_secret_request(e)
}
AnyToDeviceEvent::KeyVerificationAccept(..) AnyToDeviceEvent::KeyVerificationAccept(..)
| AnyToDeviceEvent::KeyVerificationCancel(..) | AnyToDeviceEvent::KeyVerificationCancel(..)
| AnyToDeviceEvent::KeyVerificationKey(..) | AnyToDeviceEvent::KeyVerificationKey(..)
@ -811,19 +834,26 @@ impl OlmMachine {
Ok(e) => e, Ok(e) => e,
Err(e) => { Err(e) => {
// Skip invalid events. // Skip invalid events.
warn!("Received an invalid to-device event {:?} {:?}", e, raw_event); warn!(
error =? e,
"Received an invalid to-device event"
);
continue; continue;
} }
}; };
info!("Received a to-device event {:?}", event); info!(
sender = event.sender().as_str(),
event_type = event.event_type(),
"Received a to-device event"
);
match event { match event {
AnyToDeviceEvent::RoomEncrypted(e) => { AnyToDeviceEvent::RoomEncrypted(e) => {
let decrypted = match self.decrypt_to_device_event(&e).await { let decrypted = match self.decrypt_to_device_event(&e).await {
Ok(e) => e, Ok(e) => e,
Err(err) => { Err(err) => {
warn!("Failed to decrypt to-device event from {} {}", e.sender, err); warn!(sender = e.sender.as_str(), error =? e, "Failed to decrypt to-device event");
if let OlmError::SessionWedged(sender, curve_key) = err { if let OlmError::SessionWedged(sender, curve_key) = err {
if let Err(e) = self if let Err(e) = self
@ -832,8 +862,9 @@ impl OlmMachine {
.await .await
{ {
error!( error!(
"Couldn't mark device from {} to be unwedged {:?}", sender = sender.as_str(),
sender, e error =? e,
"Couldn't mark device from to be unwedged",
); );
} }
} }
@ -1227,6 +1258,46 @@ impl OlmMachine {
Ok(exported) Ok(exported)
} }
/// Get the status of the private cross signing keys.
///
/// This can be used to check which private cross signing keys we have
/// stored locally.
pub async fn cross_signing_status(&self) -> CrossSigningStatus {
self.user_identity.lock().await.status().await
}
/// Export all the private cross signing keys we have.
///
/// The export will contain the seed for the ed25519 keys as a unpadded
/// base64 encoded string.
///
/// This method returns `None` if we don't have any private cross signing
/// keys.
pub async fn export_cross_signing_keys(&self) -> Option<CrossSigningKeyExport> {
let master_key = self.store.export_secret(&SecretName::CrossSigningMasterKey).await;
let self_signing_key =
self.store.export_secret(&SecretName::CrossSigningSelfSigningKey).await;
let user_signing_key =
self.store.export_secret(&SecretName::CrossSigningUserSigningKey).await;
if master_key.is_none() && self_signing_key.is_none() && user_signing_key.is_none() {
None
} else {
Some(CrossSigningKeyExport { master_key, self_signing_key, user_signing_key })
}
}
/// Import our private cross signing keys.
///
/// The export needs to contain the seed for the ed25519 keys as an unpadded
/// base64 encoded string.
pub async fn import_cross_signing_keys(
&self,
export: CrossSigningKeyExport,
) -> Result<CrossSigningStatus, SecretImportError> {
self.store.import_cross_signing_keys(export).await
}
} }
#[cfg(test)] #[cfg(test)]

View file

@ -32,7 +32,7 @@ use olm_rs::{
}; };
use ruma::{ use ruma::{
api::client::r0::keys::{upload_keys, upload_signatures::Request as SignatureUploadRequest}, api::client::r0::keys::{upload_keys, upload_signatures::Request as SignatureUploadRequest},
encryption::{DeviceKeys, OneTimeKey, SignedKey}, encryption::{CrossSigningKey, DeviceKeys, OneTimeKey, SignedKey},
events::{ events::{
room::encrypted::{EncryptedEventScheme, EncryptedToDeviceEventContent}, room::encrypted::{EncryptedEventScheme, EncryptedToDeviceEventContent},
AnyToDeviceEvent, ToDeviceEvent, AnyToDeviceEvent, ToDeviceEvent,
@ -52,11 +52,11 @@ use super::{
}; };
use crate::{ use crate::{
error::{EventError, OlmResult, SessionCreationError}, error::{EventError, OlmResult, SessionCreationError},
identities::ReadOnlyDevice, identities::{MasterPubkey, ReadOnlyDevice},
requests::UploadSigningKeysRequest, requests::UploadSigningKeysRequest,
store::{Changes, Store}, store::{Changes, Store},
utilities::encode, utilities::encode,
OlmError, OlmError, SignatureError,
}; };
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@ -116,13 +116,17 @@ impl Account {
&self, &self,
event: &ToDeviceEvent<EncryptedToDeviceEventContent>, event: &ToDeviceEvent<EncryptedToDeviceEventContent>,
) -> OlmResult<OlmDecryptionInfo> { ) -> OlmResult<OlmDecryptionInfo> {
debug!("Decrypting to-device event"); debug!(sender = event.sender.as_str(), "Decrypting a to-device event");
let content = if let EncryptedEventScheme::OlmV1Curve25519AesSha2(c) = &event.content.scheme let content = if let EncryptedEventScheme::OlmV1Curve25519AesSha2(c) = &event.content.scheme
{ {
c c
} else { } else {
warn!("Error, unsupported encryption algorithm"); warn!(
sender = event.sender.as_str(),
algorithm =? event.content.scheme,
"Error, unsupported encryption algorithm"
);
return Err(EventError::UnsupportedAlgorithm.into()); return Err(EventError::UnsupportedAlgorithm.into());
}; };
@ -156,6 +160,10 @@ impl Account {
Ok(d) => d, Ok(d) => d,
Err(OlmError::SessionWedged(user_id, sender_key)) => { Err(OlmError::SessionWedged(user_id, sender_key)) => {
if self.store.is_message_known(&message_hash).await? { if self.store.is_message_known(&message_hash).await? {
warn!(
sender = event.sender.as_str(),
"An Olm message got replayed, decryption failed"
);
return Err(OlmError::ReplayedMessage(user_id, sender_key)); return Err(OlmError::ReplayedMessage(user_id, sender_key));
} else { } else {
return Err(OlmError::SessionWedged(user_id, sender_key)); return Err(OlmError::SessionWedged(user_id, sender_key));
@ -164,8 +172,6 @@ impl Account {
Err(e) => return Err(e), Err(e) => return Err(e),
}; };
debug!("Decrypted a to-device event {:?}", event);
Ok(OlmDecryptionInfo { Ok(OlmDecryptionInfo {
session, session,
message_hash, message_hash,
@ -176,7 +182,10 @@ impl Account {
inbound_group_session: None, inbound_group_session: None,
}) })
} else { } else {
warn!("Olm event doesn't contain a ciphertext for our key"); warn!(
sender = event.sender.as_str(),
"Olm event doesn't contain a ciphertext for our key"
);
Err(EventError::MissingCiphertext.into()) Err(EventError::MissingCiphertext.into())
} }
} }
@ -264,9 +273,10 @@ impl Account {
// likely wedged and needs to be rotated. // likely wedged and needs to be rotated.
if matches { if matches {
warn!( warn!(
"Found a matching Olm session yet decryption failed sender = sender.as_str(),
for sender {} and sender_key {} {:?}", sender_key = sender_key,
sender, sender_key, e error =? e,
"Found a matching Olm session yet decryption failed",
); );
return Err(OlmError::SessionWedged( return Err(OlmError::SessionWedged(
sender.to_owned(), sender.to_owned(),
@ -302,9 +312,10 @@ impl Account {
// return with an error if it isn't one. // return with an error if it isn't one.
OlmMessage::Message(_) => { OlmMessage::Message(_) => {
warn!( warn!(
sender = sender.as_str(),
sender_key = sender_key,
"Failed to decrypt a non-pre-key message with all \ "Failed to decrypt a non-pre-key message with all \
available sessions {} {}", available sessions",
sender, sender_key
); );
return Err(OlmError::SessionWedged(sender.to_owned(), sender_key.to_owned())); return Err(OlmError::SessionWedged(sender.to_owned(), sender_key.to_owned()));
} }
@ -316,9 +327,11 @@ impl Account {
Ok(s) => s, Ok(s) => s,
Err(e) => { Err(e) => {
warn!( warn!(
"Failed to create a new Olm session for {} {} sender = sender.as_str(),
from a prekey message: {}", sender_key = sender_key,
sender, sender_key, e error =? e,
"Failed to create a new Olm session from a \
prekey message",
); );
return Err(OlmError::SessionWedged( return Err(OlmError::SessionWedged(
sender.to_owned(), sender.to_owned(),
@ -334,10 +347,26 @@ impl Account {
// Decrypt our message, this shouldn't fail since we're using a // Decrypt our message, this shouldn't fail since we're using a
// newly created Session. // newly created Session.
let plaintext = session.decrypt(message).await?; let plaintext = session.decrypt(message).await?;
// We need to add the new session to the session cache, otherwise
// we might try to create the same session again.
// TODO separate the session cache from the storage so we only add
// it to the cache but don't store it.
let changes = Changes {
account: Some(self.inner.clone()),
sessions: vec![session.clone()],
..Default::default()
};
self.store.save_changes(changes).await?;
(SessionType::New(session), plaintext) (SessionType::New(session), plaintext)
}; };
trace!("Successfully decrypted an Olm message: {}", plaintext); trace!(
sender = sender.as_str(),
sender_key = sender_key,
"Successfully decrypted an Olm message"
);
let (event, signing_key) = match self.parse_decrypted_to_device_event(sender, &plaintext) { let (event, signing_key) = match self.parse_decrypted_to_device_event(sender, &plaintext) {
Ok(r) => r, Ok(r) => r,
@ -732,6 +761,42 @@ impl ReadOnlyAccount {
PrivateCrossSigningIdentity::new_with_account(self).await PrivateCrossSigningIdentity::new_with_account(self).await
} }
pub(crate) async fn sign_cross_signing_key(
&self,
cross_signing_key: &mut CrossSigningKey,
) -> Result<(), SignatureError> {
let signature = self.sign_json(serde_json::to_value(&cross_signing_key)?).await;
cross_signing_key
.signatures
.entry(self.user_id().to_owned())
.or_insert_with(BTreeMap::new)
.insert(
DeviceKeyId::from_parts(DeviceKeyAlgorithm::Ed25519, self.device_id()).to_string(),
signature,
);
Ok(())
}
pub(crate) async fn sign_master_key(
&self,
master_key: MasterPubkey,
) -> Result<SignatureUploadRequest, SignatureError> {
let public_key =
master_key.get_first_key().ok_or(SignatureError::MissingSigningKey)?.to_string();
let mut cross_signing_key = master_key.into();
self.sign_cross_signing_key(&mut cross_signing_key).await?;
let mut signed_keys = BTreeMap::new();
signed_keys
.entry(self.user_id().to_owned())
.or_insert_with(BTreeMap::new)
.insert(public_key, serde_json::to_value(cross_signing_key)?);
Ok(SignatureUploadRequest::new(signed_keys))
}
/// Convert a JSON value to the canonical representation and sign the JSON /// Convert a JSON value to the canonical representation and sign the JSON
/// string. /// string.
/// ///

View file

@ -60,7 +60,8 @@ pub struct InboundGroupSession {
pub(crate) signing_keys: Arc<BTreeMap<DeviceKeyAlgorithm, String>>, pub(crate) signing_keys: Arc<BTreeMap<DeviceKeyAlgorithm, String>>,
pub(crate) room_id: Arc<RoomId>, pub(crate) room_id: Arc<RoomId>,
forwarding_chains: Arc<Vec<String>>, forwarding_chains: Arc<Vec<String>>,
imported: Arc<bool>, imported: bool,
backed_up: bool,
} }
impl InboundGroupSession { impl InboundGroupSession {
@ -103,7 +104,8 @@ impl InboundGroupSession {
signing_keys: keys.into(), signing_keys: keys.into(),
room_id: room_id.clone().into(), room_id: room_id.clone().into(),
forwarding_chains: Vec::new().into(), forwarding_chains: Vec::new().into(),
imported: false.into(), imported: false,
backed_up: false,
}) })
} }
@ -154,7 +156,8 @@ impl InboundGroupSession {
signing_keys: sender_claimed_key.into(), signing_keys: sender_claimed_key.into(),
room_id: content.room_id.clone().into(), room_id: content.room_id.clone().into(),
forwarding_chains: forwarding_chains.into(), forwarding_chains: forwarding_chains.into(),
imported: true.into(), imported: true,
backed_up: false,
}) })
} }
@ -173,7 +176,8 @@ impl InboundGroupSession {
signing_key: (&*self.signing_keys).clone(), signing_key: (&*self.signing_keys).clone(),
room_id: (&*self.room_id).clone(), room_id: (&*self.room_id).clone(),
forwarding_chains: self.forwarding_key_chain().to_vec(), forwarding_chains: self.forwarding_key_chain().to_vec(),
imported: *self.imported, imported: self.imported,
backed_up: self.backed_up,
history_visibility: self.history_visibility.as_ref().clone(), history_visibility: self.history_visibility.as_ref().clone(),
} }
} }
@ -252,7 +256,8 @@ impl InboundGroupSession {
signing_keys: pickle.signing_key.into(), signing_keys: pickle.signing_key.into(),
room_id: pickle.room_id.into(), room_id: pickle.room_id.into(),
forwarding_chains: pickle.forwarding_chains.into(), forwarding_chains: pickle.forwarding_chains.into(),
imported: pickle.imported.into(), backed_up: pickle.backed_up,
imported: pickle.imported,
}) })
} }
@ -365,6 +370,9 @@ pub struct PickledInboundGroupSession {
/// Flag remembering if the session was directly sent to us by the sender /// Flag remembering if the session was directly sent to us by the sender
/// or if it was imported. /// or if it was imported.
pub imported: bool, pub imported: bool,
/// Flag remembering if the session has been backed up.
#[serde(default)]
pub backed_up: bool,
/// History visibility of the room when the session was created. /// History visibility of the room when the session was created.
pub history_visibility: Option<HistoryVisibility>, pub history_visibility: Option<HistoryVisibility>,
} }
@ -403,7 +411,8 @@ impl TryFrom<ExportedRoomKey> for InboundGroupSession {
signing_keys: Arc::new(key.sender_claimed_keys), signing_keys: Arc::new(key.sender_claimed_keys),
room_id: Arc::new(key.room_id), room_id: Arc::new(key.room_id),
forwarding_chains: Arc::new(key.forwarding_curve25519_key_chain), forwarding_chains: Arc::new(key.forwarding_curve25519_key_chain),
imported: Arc::new(true), imported: true,
backed_up: false,
}) })
} }
} }

View file

@ -28,7 +28,7 @@ mod outbound;
pub use inbound::{InboundGroupSession, InboundGroupSessionPickle, PickledInboundGroupSession}; pub use inbound::{InboundGroupSession, InboundGroupSessionPickle, PickledInboundGroupSession};
pub use outbound::{ pub use outbound::{
EncryptionSettings, OutboundGroupSession, PickledOutboundGroupSession, ShareState, EncryptionSettings, OutboundGroupSession, PickledOutboundGroupSession, ShareInfo, ShareState,
}; };
/// The private session key of a group session. /// The private session key of a group session.

View file

@ -43,8 +43,7 @@ use ruma::{
room_key::RoomKeyToDeviceEventContent, room_key::RoomKeyToDeviceEventContent,
AnyMessageEventContent, AnyToDeviceEventContent, EventContent, AnyMessageEventContent, AnyToDeviceEventContent, EventContent,
}, },
to_device::DeviceIdOrAllDevices, DeviceId, DeviceIdBox, DeviceKeyAlgorithm, EventEncryptionAlgorithm, RoomId, UserId,
DeviceId, DeviceIdBox, EventEncryptionAlgorithm, RoomId, UserId,
}; };
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_json::json; use serde_json::json;
@ -54,13 +53,15 @@ use super::{
super::{deserialize_instant, serialize_instant}, super::{deserialize_instant, serialize_instant},
GroupSessionKey, GroupSessionKey,
}; };
use crate::ToDeviceRequest; use crate::{Device, ToDeviceRequest};
const ROTATION_PERIOD: Duration = Duration::from_millis(604800000); const ROTATION_PERIOD: Duration = Duration::from_millis(604800000);
const ROTATION_MESSAGES: u64 = 100; const ROTATION_MESSAGES: u64 = 100;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ShareState { pub enum ShareState {
NotShared, NotShared,
SharedButChangedSenderKey,
Shared(u32), Shared(u32),
} }
@ -125,8 +126,23 @@ pub struct OutboundGroupSession {
shared: Arc<AtomicBool>, shared: Arc<AtomicBool>,
invalidated: Arc<AtomicBool>, invalidated: Arc<AtomicBool>,
settings: Arc<EncryptionSettings>, settings: Arc<EncryptionSettings>,
pub(crate) shared_with_set: Arc<DashMap<UserId, DashMap<DeviceIdBox, u32>>>, pub(crate) shared_with_set: Arc<DashMap<UserId, DashMap<DeviceIdBox, ShareInfo>>>,
to_share_with_set: Arc<DashMap<Uuid, (Arc<ToDeviceRequest>, u32)>>, to_share_with_set: Arc<DashMap<Uuid, (Arc<ToDeviceRequest>, ShareInfoSet)>>,
}
/// A a map of userid/device it to a `ShareInfo`.
///
/// Holds the `ShareInfo` for all the user/device pairs that will receive the
/// room key.
pub type ShareInfoSet = BTreeMap<UserId, BTreeMap<DeviceIdBox, ShareInfo>>;
/// Struct holding info about the share state of a outbound group session.
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct ShareInfo {
/// The sender key of the device that was used to encrypt the room key.
pub sender_key: String,
/// The message index that the device received.
pub message_index: u32,
} }
impl OutboundGroupSession { impl OutboundGroupSession {
@ -174,9 +190,9 @@ impl OutboundGroupSession {
&self, &self,
request_id: Uuid, request_id: Uuid,
request: Arc<ToDeviceRequest>, request: Arc<ToDeviceRequest>,
message_index: u32, share_infos: ShareInfoSet,
) { ) {
self.to_share_with_set.insert(request_id, (request, message_index)); self.to_share_with_set.insert(request_id, (request, share_infos));
} }
/// This should be called if an the user wishes to rotate this session. /// This should be called if an the user wishes to rotate this session.
@ -194,28 +210,15 @@ impl OutboundGroupSession {
/// This removes the request from the queue and marks the set of /// This removes the request from the queue and marks the set of
/// users/devices that received the session. /// users/devices that received the session.
pub fn mark_request_as_sent(&self, request_id: &Uuid) { pub fn mark_request_as_sent(&self, request_id: &Uuid) {
if let Some((_, r)) = self.to_share_with_set.remove(request_id) { if let Some((_, (_, r))) = self.to_share_with_set.remove(request_id) {
trace!( trace!(
request_id = request_id.to_string().as_str(), request_id = request_id.to_string().as_str(),
"Marking to-device request carrying a room key as sent" "Marking to-device request carrying a room key as sent"
); );
let user_pairs = r.0.messages.iter().map(|(u, v)| { for (user_id, info) in r.into_iter() {
( self.shared_with_set.entry(user_id).or_insert_with(DashMap::new).extend(info)
u.clone(), }
v.iter().filter_map(|d| {
if let DeviceIdOrAllDevices::DeviceId(d) = d.0 {
Some((d.clone(), r.1))
} else {
None
}
}),
)
});
user_pairs.for_each(|(u, d)| {
self.shared_with_set.entry(u).or_insert_with(DashMap::new).extend(d);
});
if self.to_share_with_set.is_empty() { if self.to_share_with_set.is_empty() {
debug!( debug!(
@ -368,36 +371,39 @@ impl OutboundGroupSession {
} }
/// Has or will the session be shared with the given user/device pair. /// Has or will the session be shared with the given user/device pair.
pub(crate) fn is_shared_with(&self, user_id: &UserId, device_id: &DeviceId) -> ShareState { pub(crate) fn is_shared_with(&self, device: &Device) -> ShareState {
// Check if we shared the session. // Check if we shared the session.
let shared_state = self let shared_state = self.shared_with_set.get(device.user_id()).and_then(|d| {
.shared_with_set d.get(device.device_id()).map(|s| {
.get(user_id) if Some(&s.sender_key) == device.get_key(DeviceKeyAlgorithm::Curve25519) {
.and_then(|d| d.get(device_id).map(|m| ShareState::Shared(*m.value()))); ShareState::Shared(s.message_index)
} else {
ShareState::SharedButChangedSenderKey
}
})
});
if let Some(state) = shared_state { if let Some(state) = shared_state {
state state
} else { } else {
// If we haven't shared the session, check if we're going to share // If we haven't shared the session, check if we're going to share
// the session. // the session.
let device_id = DeviceIdOrAllDevices::DeviceId(device_id.into());
// Find the first request that contains the given user id and // Find the first request that contains the given user id and
// device id. // device id.
let shared = self.to_share_with_set.iter().find_map(|item| { let shared = self.to_share_with_set.iter().find_map(|item| {
let request = &item.value().0; let share_info = &item.value().1;
let message_index = item.value().1;
if request share_info.get(device.user_id()).and_then(|d| {
.messages d.get(device.device_id()).map(|info| {
.get(user_id) if Some(&info.sender_key) == device.get_key(DeviceKeyAlgorithm::Curve25519)
.map(|e| e.contains_key(&device_id)) {
.unwrap_or(false) ShareState::Shared(info.message_index)
{ } else {
Some(ShareState::Shared(message_index)) ShareState::SharedButChangedSenderKey
} else { }
None })
} })
}); });
shared.unwrap_or(ShareState::NotShared) shared.unwrap_or(ShareState::NotShared)
@ -406,11 +412,11 @@ impl OutboundGroupSession {
/// Mark that the session was shared with the given user/device pair. /// Mark that the session was shared with the given user/device pair.
#[cfg(test)] #[cfg(test)]
pub fn mark_shared_with(&self, user_id: &UserId, device_id: &DeviceId) { pub fn mark_shared_with(&self, user_id: &UserId, device_id: &DeviceId, sender_key: &str) {
self.shared_with_set self.shared_with_set.entry(user_id.to_owned()).or_insert_with(DashMap::new).insert(
.entry(user_id.to_owned()) device_id.to_owned(),
.or_insert_with(DashMap::new) ShareInfo { sender_key: sender_key.to_owned(), message_index: 0 },
.insert(device_id.to_owned(), 0); );
} }
/// Get the list of requests that need to be sent out for this session to be /// Get the list of requests that need to be sent out for this session to be
@ -498,8 +504,7 @@ impl OutboundGroupSession {
.map(|u| { .map(|u| {
( (
u.key().clone(), u.key().clone(),
#[allow(clippy::map_clone)] u.value().iter().map(|d| (d.key().clone(), d.value().clone())).collect(),
u.value().iter().map(|d| (d.key().clone(), *d.value())).collect(),
) )
}) })
.collect(), .collect(),
@ -555,9 +560,9 @@ pub struct PickledOutboundGroupSession {
/// Has the session been invalidated. /// Has the session been invalidated.
pub invalidated: bool, pub invalidated: bool,
/// The set of users the session has been already shared with. /// The set of users the session has been already shared with.
pub shared_with_set: BTreeMap<UserId, BTreeMap<DeviceIdBox, u32>>, pub shared_with_set: BTreeMap<UserId, BTreeMap<DeviceIdBox, ShareInfo>>,
/// Requests that need to be sent out to share the session. /// Requests that need to be sent out to share the session.
pub requests: BTreeMap<Uuid, (Arc<ToDeviceRequest>, u32)>, pub requests: BTreeMap<Uuid, (Arc<ToDeviceRequest>, ShareInfoSet)>,
} }
#[cfg(test)] #[cfg(test)]

View file

@ -27,14 +27,14 @@ pub(crate) use account::{Account, OlmDecryptionInfo, SessionType};
pub use account::{AccountPickle, OlmMessageHash, PickledAccount, ReadOnlyAccount}; pub use account::{AccountPickle, OlmMessageHash, PickledAccount, ReadOnlyAccount};
pub use group_sessions::{ pub use group_sessions::{
EncryptionSettings, ExportedRoomKey, InboundGroupSession, InboundGroupSessionPickle, EncryptionSettings, ExportedRoomKey, InboundGroupSession, InboundGroupSessionPickle,
OutboundGroupSession, PickledInboundGroupSession, PickledOutboundGroupSession, OutboundGroupSession, PickledInboundGroupSession, PickledOutboundGroupSession, ShareInfo,
}; };
pub(crate) use group_sessions::{GroupSessionKey, ShareState}; pub(crate) use group_sessions::{GroupSessionKey, ShareState};
use matrix_sdk_common::instant::{Duration, Instant}; use matrix_sdk_common::instant::{Duration, Instant};
pub use olm_rs::{account::IdentityKeys, PicklingMode}; pub use olm_rs::{account::IdentityKeys, PicklingMode};
use serde::{Deserialize, Deserializer, Serialize, Serializer}; use serde::{Deserialize, Deserializer, Serialize, Serializer};
pub use session::{PickledSession, Session, SessionPickle}; pub use session::{PickledSession, Session, SessionPickle};
pub use signing::{PickledCrossSigningIdentity, PrivateCrossSigningIdentity}; pub use signing::{CrossSigningStatus, PickledCrossSigningIdentity, PrivateCrossSigningIdentity};
pub(crate) use utility::Utility; pub(crate) use utility::Utility;
pub(crate) fn serialize_instant<S>(instant: &Instant, serializer: S) -> Result<S::Ok, S::Error> pub(crate) fn serialize_instant<S>(instant: &Instant, serializer: S) -> Result<S::Ok, S::Error>
@ -267,7 +267,7 @@ pub(crate) mod test {
}) })
.to_string(); .to_string();
let event: AnySyncRoomEvent = serde_json::from_str(&event).expect("WHAAAT?!?!?"); let event: AnySyncRoomEvent = serde_json::from_str(&event).unwrap();
let event = let event =
if let AnySyncRoomEvent::Message(AnySyncMessageEvent::RoomEncrypted(event)) = event { if let AnySyncRoomEvent::Message(AnySyncMessageEvent::RoomEncrypted(event)) = event {

View file

@ -27,14 +27,20 @@ use pk_signing::{MasterSigning, PickledSignings, SelfSigning, Signing, SigningEr
use ruma::{ use ruma::{
api::client::r0::keys::upload_signatures::Request as SignatureUploadRequest, api::client::r0::keys::upload_signatures::Request as SignatureUploadRequest,
encryption::{DeviceKeys, KeyUsage}, encryption::{DeviceKeys, KeyUsage},
DeviceKeyAlgorithm, DeviceKeyId, UserId, events::secret::request::SecretName,
UserId,
}; };
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_json::Error as JsonError; use serde_json::Error as JsonError;
use crate::{ use crate::{
error::SignatureError, identities::MasterPubkey, requests::UploadSigningKeysRequest, error::SignatureError,
ReadOnlyAccount, ReadOnlyDevice, ReadOnlyOwnUserIdentity, ReadOnlyUserIdentity, identities::{MasterPubkey, SelfSigningPubkey, UserSigningPubkey},
requests::UploadSigningKeysRequest,
store::SecretImportError,
utilities::decode,
OwnUserIdentity, ReadOnlyAccount, ReadOnlyDevice, ReadOnlyOwnUserIdentity,
ReadOnlyUserIdentity,
}; };
/// Private cross signing identity. /// Private cross signing identity.
@ -55,6 +61,26 @@ pub struct PrivateCrossSigningIdentity {
pub(crate) self_signing_key: Arc<Mutex<Option<SelfSigning>>>, pub(crate) self_signing_key: Arc<Mutex<Option<SelfSigning>>>,
} }
/// A struct containing information if any of our cross signing keys were
/// cleared because the public keys differ from the keys that are uploaded to
/// the server.
#[derive(Debug, Clone)]
pub struct ClearResult {
/// Was the master key cleared.
master_cleared: bool,
/// Was the self-signing key cleared.
self_signing_cleared: bool,
/// Was the user-signing key cleared.
user_signing_cleared: bool,
}
impl ClearResult {
/// Did we clear any of the private cross signing keys.
pub fn any_cleared(&self) -> bool {
self.master_cleared || self.self_signing_cleared || self.user_signing_cleared
}
}
/// The pickled version of a `PrivateCrossSigningIdentity`. /// The pickled version of a `PrivateCrossSigningIdentity`.
/// ///
/// Can be used to store the identity. /// Can be used to store the identity.
@ -68,6 +94,20 @@ pub struct PickledCrossSigningIdentity {
pub pickle: String, pub pickle: String,
} }
/// Struct representing the state of our private cross signing keys, it shows
/// which private cross signing keys we have locally stored.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CrossSigningStatus {
/// Do we have the master key.
pub has_master: bool,
/// Do we have the self signing key, this one is necessary to sign our own
/// devices.
pub has_self_signing: bool,
/// Do we have the user signing key, this one is necessary to sign other
/// users.
pub has_user_signing: bool,
}
impl PrivateCrossSigningIdentity { impl PrivateCrossSigningIdentity {
/// Get the user id that this identity belongs to. /// Get the user id that this identity belongs to.
pub fn user_id(&self) -> &UserId { pub fn user_id(&self) -> &UserId {
@ -96,16 +136,213 @@ impl PrivateCrossSigningIdentity {
self.self_signing_key.lock().await.is_some() self.self_signing_key.lock().await.is_some()
} }
/// Can we sign other users, i.e. do we have a user signing key.
pub async fn can_sign_users(&self) -> bool {
self.user_signing_key.lock().await.is_some()
}
/// Do we have the master key. /// Do we have the master key.
pub async fn has_master_key(&self) -> bool { pub async fn has_master_key(&self) -> bool {
self.master_key.lock().await.is_some() self.master_key.lock().await.is_some()
} }
/// Get the status of our private cross signing keys, i.e. if we have the
/// master key and the subkeys.
pub async fn status(&self) -> CrossSigningStatus {
CrossSigningStatus {
has_master: self.has_master_key().await,
has_self_signing: self.can_sign_devices().await,
has_user_signing: self.can_sign_users().await,
}
}
/// Get the public part of the master key, if we have one. /// Get the public part of the master key, if we have one.
pub async fn master_public_key(&self) -> Option<MasterPubkey> { pub async fn master_public_key(&self) -> Option<MasterPubkey> {
self.master_key.lock().await.as_ref().map(|m| m.public_key.to_owned()) self.master_key.lock().await.as_ref().map(|m| m.public_key.to_owned())
} }
/// Get the public part of the self-signing key, if we have one.
pub async fn self_signing_public_key(&self) -> Option<SelfSigningPubkey> {
self.self_signing_key.lock().await.as_ref().map(|k| k.public_key.to_owned())
}
/// Get the public part of the user-signing key, if we have one.
pub async fn user_signing_public_key(&self) -> Option<UserSigningPubkey> {
self.user_signing_key.lock().await.as_ref().map(|k| k.public_key.to_owned())
}
/// Export the seed of the private cross signing key
///
/// The exported seed will be encoded as unpadded base64.
///
/// # Arguments
///
/// * `secret_name` - The type of the cross signing key that should be
/// exported.
pub async fn export_secret(&self, secret_name: &SecretName) -> Option<String> {
match secret_name {
SecretName::CrossSigningMasterKey => {
self.master_key.lock().await.as_ref().map(|m| m.export_seed())
}
SecretName::CrossSigningUserSigningKey => {
self.user_signing_key.lock().await.as_ref().map(|m| m.export_seed())
}
SecretName::CrossSigningSelfSigningKey => {
self.self_signing_key.lock().await.as_ref().map(|m| m.export_seed())
}
_ => None,
}
}
pub(crate) async fn import_secret(
&self,
public_identity: OwnUserIdentity,
secret_name: &SecretName,
seed: &str,
) -> Result<(), SecretImportError> {
let (master, self_signing, user_signing) = match secret_name {
SecretName::CrossSigningMasterKey => (Some(seed), None, None),
SecretName::CrossSigningSelfSigningKey => (None, Some(seed), None),
SecretName::CrossSigningUserSigningKey => (None, None, Some(seed)),
_ => return Ok(()),
};
self.import_secrets(public_identity, master, self_signing, user_signing).await
}
pub(crate) async fn import_secrets(
&self,
public_identity: OwnUserIdentity,
master_key: Option<&str>,
self_signing_key: Option<&str>,
user_signing_key: Option<&str>,
) -> Result<(), SecretImportError> {
let master = if let Some(master_key) = master_key {
let seed = decode(master_key)?;
let master = MasterSigning::from_seed(self.user_id().clone(), seed);
if public_identity.master_key() == &master.public_key {
Ok(Some(master))
} else {
Err(SecretImportError::MissmatchedPublicKeys)
}
} else {
Ok(None)
}?;
let user_signing = if let Some(user_signing_key) = user_signing_key {
let seed = decode(user_signing_key)?;
let subkey = UserSigning::from_seed(self.user_id().clone(), seed);
if public_identity.user_signing_key() == &subkey.public_key {
Ok(Some(subkey))
} else {
Err(SecretImportError::MissmatchedPublicKeys)
}
} else {
Ok(None)
}?;
let self_signing = if let Some(self_signing_key) = self_signing_key {
let seed = decode(self_signing_key)?;
let subkey = SelfSigning::from_seed(self.user_id().clone(), seed);
if public_identity.self_signing_key() == &subkey.public_key {
Ok(Some(subkey))
} else {
Err(SecretImportError::MissmatchedPublicKeys)
}
} else {
Ok(None)
}?;
if let Some(master) = master {
*self.master_key.lock().await = Some(master);
}
if let Some(self_signing) = self_signing {
*self.self_signing_key.lock().await = Some(self_signing);
}
if let Some(user_signing) = user_signing {
*self.user_signing_key.lock().await = Some(user_signing);
}
Ok(())
}
/// Remove our private cross signing key if the public keys differ from
/// what's found in the `ReadOnlyOwnUserIdentity`.
pub(crate) async fn clear_if_differs(
&self,
public_identity: &ReadOnlyOwnUserIdentity,
) -> ClearResult {
let result = self.get_public_identity_diff(public_identity).await;
if result.master_cleared {
*self.master_key.lock().await = None;
}
if result.user_signing_cleared {
*self.user_signing_key.lock().await = None;
}
if result.self_signing_cleared {
*self.self_signing_key.lock().await = None;
}
result
}
async fn get_public_identity_diff(
&self,
public_identity: &ReadOnlyOwnUserIdentity,
) -> ClearResult {
let master_differs = self
.master_public_key()
.await
.map(|master| &master != public_identity.master_key())
.unwrap_or(false);
let user_signing_differs = self
.user_signing_public_key()
.await
.map(|subkey| &subkey != public_identity.user_signing_key())
.unwrap_or(false);
let self_signing_differs = self
.self_signing_public_key()
.await
.map(|subkey| &subkey != public_identity.self_signing_key())
.unwrap_or(false);
ClearResult {
master_cleared: master_differs,
user_signing_cleared: user_signing_differs,
self_signing_cleared: self_signing_differs,
}
}
/// Get the names of the secrets we are missing.
pub(crate) async fn get_missing_secrets(&self) -> Vec<SecretName> {
let mut missing = Vec::new();
if !self.has_master_key().await {
missing.push(SecretName::CrossSigningMasterKey);
}
if !self.can_sign_devices().await {
missing.push(SecretName::CrossSigningSelfSigningKey);
}
if !self.can_sign_users().await {
missing.push(SecretName::CrossSigningUserSigningKey);
}
missing
}
/// Create a new empty identity. /// Create a new empty identity.
pub(crate) fn empty(user_id: UserId) -> Self { pub(crate) fn empty(user_id: UserId) -> Self {
Self { Self {
@ -128,6 +365,7 @@ impl PrivateCrossSigningIdentity {
.ok_or(SignatureError::MissingSigningKey)? .ok_or(SignatureError::MissingSigningKey)?
.public_key .public_key
.clone(); .clone();
let self_signing = self let self_signing = self
.self_signing_key .self_signing_key
.lock() .lock()
@ -136,6 +374,7 @@ impl PrivateCrossSigningIdentity {
.ok_or(SignatureError::MissingSigningKey)? .ok_or(SignatureError::MissingSigningKey)?
.public_key .public_key
.clone(); .clone();
let user_signing = self let user_signing = self
.user_signing_key .user_signing_key
.lock() .lock()
@ -144,6 +383,7 @@ impl PrivateCrossSigningIdentity {
.ok_or(SignatureError::MissingSigningKey)? .ok_or(SignatureError::MissingSigningKey)?
.public_key .public_key
.clone(); .clone();
let identity = ReadOnlyOwnUserIdentity::new(master, self_signing, user_signing)?; let identity = ReadOnlyOwnUserIdentity::new(master, self_signing, user_signing)?;
identity.mark_as_verified(); identity.mark_as_verified();
@ -155,7 +395,7 @@ impl PrivateCrossSigningIdentity {
&self, &self,
user_identity: &ReadOnlyUserIdentity, user_identity: &ReadOnlyUserIdentity,
) -> Result<SignatureUploadRequest, SignatureError> { ) -> Result<SignatureUploadRequest, SignatureError> {
let signed_keys = self let master_key = self
.user_signing_key .user_signing_key
.lock() .lock()
.await .await
@ -164,6 +404,17 @@ impl PrivateCrossSigningIdentity {
.sign_user(user_identity) .sign_user(user_identity)
.await?; .await?;
let mut signed_keys = BTreeMap::new();
signed_keys.entry(user_identity.user_id().to_owned()).or_insert_with(BTreeMap::new).insert(
user_identity
.master_key()
.get_first_key()
.ok_or(SignatureError::MissingSigningKey)?
.to_owned(),
serde_json::to_value(master_key)?,
);
Ok(SignatureUploadRequest::new(signed_keys)) Ok(SignatureUploadRequest::new(signed_keys))
} }
@ -225,22 +476,11 @@ impl PrivateCrossSigningIdentity {
let mut public_key = let mut public_key =
master.cross_signing_key(account.user_id().to_owned(), KeyUsage::Master); master.cross_signing_key(account.user_id().to_owned(), KeyUsage::Master);
let signature = account
.sign_json(
serde_json::to_value(&public_key)
.expect("Can't convert own public master key to json"),
)
.await;
public_key account
.signatures .sign_cross_signing_key(&mut public_key)
.entry(account.user_id().to_owned()) .await
.or_insert_with(BTreeMap::new) .expect("Can't sign our freshly created master key with our account");
.insert(
DeviceKeyId::from_parts(DeviceKeyAlgorithm::Ed25519, account.device_id())
.to_string(),
signature,
);
let master = MasterSigning { inner: master, public_key: public_key.into() }; let master = MasterSigning { inner: master, public_key: public_key.into() };
@ -290,6 +530,12 @@ impl PrivateCrossSigningIdentity {
Self::new_helper(&user_id, master).await Self::new_helper(&user_id, master).await
} }
#[cfg(test)]
pub(crate) async fn reset(&mut self) {
let new = Self::new(self.user_id().to_owned()).await;
*self = new
}
/// Mark the identity as shared. /// Mark the identity as shared.
pub fn mark_as_shared(&self) { pub fn mark_as_shared(&self) {
self.shared.store(true, Ordering::SeqCst) self.shared.store(true, Ordering::SeqCst)
@ -357,23 +603,18 @@ impl PrivateCrossSigningIdentity {
) -> Result<Self, SigningError> { ) -> Result<Self, SigningError> {
let signings: PickledSignings = serde_json::from_str(&pickle.pickle)?; let signings: PickledSignings = serde_json::from_str(&pickle.pickle)?;
let master = if let Some(m) = signings.master_key { let master =
Some(MasterSigning::from_pickle(m, pickle_key)?) signings.master_key.map(|m| MasterSigning::from_pickle(m, pickle_key)).transpose()?;
} else {
None
};
let self_signing = if let Some(s) = signings.self_signing_key { let self_signing = signings
Some(SelfSigning::from_pickle(s, pickle_key)?) .self_signing_key
} else { .map(|s| SelfSigning::from_pickle(s, pickle_key))
None .transpose()?;
};
let user_signing = if let Some(u) = signings.user_signing_key { let user_signing = signings
Some(UserSigning::from_pickle(u, pickle_key)?) .user_signing_key
} else { .map(|s| UserSigning::from_pickle(s, pickle_key))
None .transpose()?;
};
Ok(Self { Ok(Self {
user_id: Arc::new(pickle.user_id), user_id: Arc::new(pickle.user_id),
@ -388,13 +629,13 @@ impl PrivateCrossSigningIdentity {
/// identity. /// identity.
pub(crate) async fn as_upload_request(&self) -> UploadSigningKeysRequest { pub(crate) async fn as_upload_request(&self) -> UploadSigningKeysRequest {
let master_key = let master_key =
self.master_key.lock().await.as_ref().cloned().map(|k| k.public_key.into()); self.master_key.lock().await.as_ref().map(|k| k.public_key.to_owned().into());
let user_signing_key = let user_signing_key =
self.user_signing_key.lock().await.as_ref().cloned().map(|k| k.public_key.into()); self.user_signing_key.lock().await.as_ref().map(|k| k.public_key.to_owned().into());
let self_signing_key = let self_signing_key =
self.self_signing_key.lock().await.as_ref().cloned().map(|k| k.public_key.into()); self.self_signing_key.lock().await.as_ref().map(|k| k.public_key.to_owned().into());
UploadSigningKeysRequest { master_key, self_signing_key, user_signing_key } UploadSigningKeysRequest { master_key, self_signing_key, user_signing_key }
} }
@ -402,10 +643,10 @@ impl PrivateCrossSigningIdentity {
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use std::{collections::BTreeMap, sync::Arc}; use std::sync::Arc;
use matrix_sdk_test::async_test; use matrix_sdk_test::async_test;
use ruma::{encryption::CrossSigningKey, user_id, UserId}; use ruma::{user_id, UserId};
use super::{PrivateCrossSigningIdentity, Signing}; use super::{PrivateCrossSigningIdentity, Signing};
use crate::{ use crate::{
@ -525,24 +766,7 @@ mod test {
let user_signing = identity.user_signing_key.lock().await; let user_signing = identity.user_signing_key.lock().await;
let user_signing = user_signing.as_ref().unwrap(); let user_signing = user_signing.as_ref().unwrap();
let signatures = user_signing.sign_user(&bob_public).await.unwrap(); let master = user_signing.sign_user(&bob_public).await.unwrap();
let (key_id, signature) = signatures
.iter()
.next()
.unwrap()
.1
.iter()
.next()
.map(|(k, s)| (k.to_string(), serde_json::from_value(s.to_owned()).unwrap()))
.unwrap();
let mut master: CrossSigningKey = bob_public.master_key.as_ref().clone();
master
.signatures
.entry(identity.user_id().to_owned())
.or_insert_with(BTreeMap::new)
.insert(key_id, signature);
bob_public.master_key = master.into(); bob_public.master_key = master.into();

View file

@ -24,7 +24,7 @@ use olm_rs::pk::OlmPkSigning;
#[cfg(test)] #[cfg(test)]
use olm_rs::{errors::OlmUtilityError, utility::OlmUtility}; use olm_rs::{errors::OlmUtilityError, utility::OlmUtility};
use ruma::{ use ruma::{
encryption::{CrossSigningKey, DeviceKeys, KeyUsage}, encryption::{CrossSigningKey, CrossSigningKeySignatures, DeviceKeys, KeyUsage},
serde::CanonicalJsonValue, serde::CanonicalJsonValue,
DeviceKeyAlgorithm, DeviceKeyId, UserId, DeviceKeyAlgorithm, DeviceKeyId, UserId,
}; };
@ -36,7 +36,7 @@ use zeroize::Zeroizing;
use crate::{ use crate::{
error::SignatureError, error::SignatureError,
identities::{MasterPubkey, SelfSigningPubkey, UserSigningPubkey}, identities::{MasterPubkey, SelfSigningPubkey, UserSigningPubkey},
utilities::{decode_url_safe as decode, encode_url_safe as encode, DecodeError}, utilities::{decode_url_safe, encode, encode_url_safe, DecodeError},
ReadOnlyUserIdentity, ReadOnlyUserIdentity,
}; };
@ -135,6 +135,17 @@ impl MasterSigning {
PickledMasterSigning { pickle, public_key } PickledMasterSigning { pickle, public_key }
} }
pub fn export_seed(&self) -> String {
encode(self.inner.seed.as_slice())
}
pub fn from_seed(user_id: UserId, seed: Vec<u8>) -> Self {
let inner = Signing::from_seed(seed);
let public_key = inner.cross_signing_key(user_id, KeyUsage::Master).into();
Self { inner, public_key }
}
pub fn from_pickle( pub fn from_pickle(
pickle: PickledMasterSigning, pickle: PickledMasterSigning,
pickle_key: &[u8], pickle_key: &[u8],
@ -177,10 +188,33 @@ impl UserSigning {
PickledUserSigning { pickle, public_key } PickledUserSigning { pickle, public_key }
} }
pub fn export_seed(&self) -> String {
encode(self.inner.seed.as_slice())
}
pub fn from_seed(user_id: UserId, seed: Vec<u8>) -> Self {
let inner = Signing::from_seed(seed);
let public_key = inner.cross_signing_key(user_id, KeyUsage::UserSigning).into();
Self { inner, public_key }
}
pub async fn sign_user( pub async fn sign_user(
&self, &self,
user: &ReadOnlyUserIdentity, user: &ReadOnlyUserIdentity,
) -> Result<BTreeMap<UserId, BTreeMap<String, Value>>, SignatureError> { ) -> Result<CrossSigningKey, SignatureError> {
let signature = self.sign_user_helper(user).await?;
let mut master_key: CrossSigningKey = user.master_key().to_owned().into();
master_key.signatures.extend(signature);
Ok(master_key)
}
pub async fn sign_user_helper(
&self,
user: &ReadOnlyUserIdentity,
) -> Result<CrossSigningKeySignatures, SignatureError> {
let user_master: &CrossSigningKey = user.master_key().as_ref(); let user_master: &CrossSigningKey = user.master_key().as_ref();
let signature = self.inner.sign_json(serde_json::to_value(user_master)?).await?; let signature = self.inner.sign_json(serde_json::to_value(user_master)?).await?;
@ -195,7 +229,7 @@ impl UserSigning {
self.inner.public_key.as_str().into(), self.inner.public_key.as_str().into(),
) )
.to_string(), .to_string(),
serde_json::to_value(signature.0)?, signature.0,
); );
Ok(signatures) Ok(signatures)
@ -218,6 +252,17 @@ impl SelfSigning {
PickledSelfSigning { pickle, public_key } PickledSelfSigning { pickle, public_key }
} }
pub fn export_seed(&self) -> String {
encode(self.inner.seed.as_slice())
}
pub fn from_seed(user_id: UserId, seed: Vec<u8>) -> Self {
let inner = Signing::from_seed(seed);
let public_key = inner.cross_signing_key(user_id, KeyUsage::SelfSigning).into();
Self { inner, public_key }
}
pub async fn sign_device_helper(&self, value: Value) -> Result<Signature, SignatureError> { pub async fn sign_device_helper(&self, value: Value) -> Result<Signature, SignatureError> {
self.inner.sign_json(value).await self.inner.sign_json(value).await
} }
@ -313,9 +358,9 @@ impl Signing {
let key = GenericArray::from_slice(pickle_key); let key = GenericArray::from_slice(pickle_key);
let cipher = Aes256Gcm::new(key); let cipher = Aes256Gcm::new(key);
let nonce = decode(pickled.nonce)?; let nonce = decode_url_safe(pickled.nonce)?;
let nonce = GenericArray::from_slice(&nonce); let nonce = GenericArray::from_slice(&nonce);
let ciphertext = &decode(pickled.ciphertext)?; let ciphertext = &decode_url_safe(pickled.ciphertext)?;
let seed = cipher let seed = cipher
.decrypt(nonce, ciphertext.as_slice()) .decrypt(nonce, ciphertext.as_slice())
@ -335,9 +380,10 @@ impl Signing {
let ciphertext = let ciphertext =
cipher.encrypt(nonce, self.seed.as_slice()).expect("Can't encrypt signing pickle"); cipher.encrypt(nonce, self.seed.as_slice()).expect("Can't encrypt signing pickle");
let ciphertext = encode(ciphertext); let ciphertext = encode_url_safe(ciphertext);
let pickle = InnerPickle { version: 1, nonce: encode(nonce.as_slice()), ciphertext }; let pickle =
InnerPickle { version: 1, nonce: encode_url_safe(nonce.as_slice()), ciphertext };
PickledSigning(serde_json::to_string(&pickle).expect("Can't encode pickled signing")) PickledSigning(serde_json::to_string(&pickle).expect("Can't encode pickled signing"))
} }

View file

@ -18,7 +18,7 @@ use matrix_sdk_common::uuid::Uuid;
use ruma::{ use ruma::{
api::client::r0::{ api::client::r0::{
keys::{ keys::{
claim_keys::Response as KeysClaimResponse, claim_keys::{Request as KeysClaimRequest, Response as KeysClaimResponse},
get_keys::Response as KeysQueryResponse, get_keys::Response as KeysQueryResponse,
upload_keys::{Request as KeysUploadRequest, Response as KeysUploadResponse}, upload_keys::{Request as KeysUploadRequest, Response as KeysUploadResponse},
upload_signatures::{ upload_signatures::{
@ -183,6 +183,10 @@ pub enum OutgoingRequests {
/// The keys query request, fetching the device and cross singing keys of /// The keys query request, fetching the device and cross singing keys of
/// other users. /// other users.
KeysQuery(KeysQueryRequest), KeysQuery(KeysQueryRequest),
/// The request to claim one-time keys for a user/device pair from the
/// server, after the response is received an 1-to-1 Olm session will be
/// established with the user/device pair.
KeysClaim(KeysClaimRequest),
/// The to-device requests, this request is used for a couple of different /// The to-device requests, this request is used for a couple of different
/// things, the main use is key requests/forwards and interactive device /// things, the main use is key requests/forwards and interactive device
/// verification. /// verification.
@ -211,6 +215,12 @@ impl From<KeysQueryRequest> for OutgoingRequests {
} }
} }
impl From<KeysClaimRequest> for OutgoingRequests {
fn from(r: KeysClaimRequest) -> Self {
Self::KeysClaim(r)
}
}
impl From<KeysUploadRequest> for OutgoingRequests { impl From<KeysUploadRequest> for OutgoingRequests {
fn from(request: KeysUploadRequest) -> Self { fn from(request: KeysUploadRequest) -> Self {
OutgoingRequests::KeysUpload(request) OutgoingRequests::KeysUpload(request)

View file

@ -33,7 +33,7 @@ use tracing::{debug, info, trace};
use crate::{ use crate::{
error::{EventError, MegolmResult, OlmResult}, error::{EventError, MegolmResult, OlmResult},
olm::{Account, InboundGroupSession, OutboundGroupSession, Session, ShareState}, olm::{Account, InboundGroupSession, OutboundGroupSession, Session, ShareInfo, ShareState},
store::{Changes, Result as StoreResult, Store}, store::{Changes, Result as StoreResult, Store},
Device, EncryptionSettings, OlmError, ToDeviceRequest, Device, EncryptionSettings, OlmError, ToDeviceRequest,
}; };
@ -226,21 +226,43 @@ impl GroupSessionManager {
async fn encrypt_session_for( async fn encrypt_session_for(
content: AnyToDeviceEventContent, content: AnyToDeviceEventContent,
devices: Vec<Device>, devices: Vec<Device>,
) -> OlmResult<(Uuid, ToDeviceRequest, Vec<Session>)> { message_index: u32,
) -> OlmResult<(
Uuid,
ToDeviceRequest,
BTreeMap<UserId, BTreeMap<DeviceIdBox, ShareInfo>>,
Vec<Session>,
)> {
let mut messages = BTreeMap::new(); let mut messages = BTreeMap::new();
let mut changed_sessions = Vec::new(); let mut changed_sessions = Vec::new();
let mut share_infos = BTreeMap::new();
let encrypt = |device: Device, content: AnyToDeviceEventContent| async move { let encrypt = |device: Device, content: AnyToDeviceEventContent| async move {
let mut message = BTreeMap::new(); let mut message = BTreeMap::new();
let mut share_infos = BTreeMap::new();
let encrypted = device.encrypt(content.clone()).await; let encrypted = device.encrypt(content.clone()).await;
let used_session = match encrypted { let used_session = match encrypted {
Ok((session, encrypted)) => { Ok((session, encrypted)) => {
message.entry(device.user_id().clone()).or_insert_with(BTreeMap::new).insert( message
DeviceIdOrAllDevices::DeviceId(device.device_id().into()), .entry(device.user_id().to_owned())
Raw::from(AnyToDeviceEventContent::RoomEncrypted(encrypted)), .or_insert_with(BTreeMap::new)
); .insert(
DeviceIdOrAllDevices::DeviceId(device.device_id().into()),
Raw::from(AnyToDeviceEventContent::RoomEncrypted(encrypted)),
);
share_infos
.entry(device.user_id().to_owned())
.or_insert_with(BTreeMap::new)
.insert(
device.device_id().to_owned(),
ShareInfo {
sender_key: session.sender_key().to_owned(),
message_index,
},
);
Some(session) Some(session)
} }
// TODO we'll want to create m.room_key.withheld here. // TODO we'll want to create m.room_key.withheld here.
@ -249,7 +271,7 @@ impl GroupSessionManager {
Err(e) => return Err(e), Err(e) => return Err(e),
}; };
Ok((used_session, message)) Ok((used_session, share_infos, message))
}; };
let tasks: Vec<_> = let tasks: Vec<_> =
@ -258,7 +280,7 @@ impl GroupSessionManager {
let results = join_all(tasks).await; let results = join_all(tasks).await;
for result in results { for result in results {
let (used_session, message) = result.expect("Encryption task panicked")?; let (used_session, infos, message) = result.expect("Encryption task panicked")?;
if let Some(session) = used_session { if let Some(session) = used_session {
changed_sessions.push(session); changed_sessions.push(session);
@ -267,6 +289,10 @@ impl GroupSessionManager {
for (user, device_messages) in message.into_iter() { for (user, device_messages) in message.into_iter() {
messages.entry(user).or_insert_with(BTreeMap::new).extend(device_messages); messages.entry(user).or_insert_with(BTreeMap::new).extend(device_messages);
} }
for (user, infos) in infos.into_iter() {
share_infos.entry(user).or_insert_with(BTreeMap::new).extend(infos);
}
} }
let id = Uuid::new_v4(); let id = Uuid::new_v4();
@ -280,7 +306,7 @@ impl GroupSessionManager {
"Created a to-device request carrying a room_key" "Created a to-device request carrying a room_key"
); );
Ok((id, request, changed_sessions)) Ok((id, request, share_infos, changed_sessions))
} }
/// Given a list of user and an outbound session, return the list of users /// Given a list of user and an outbound session, return the list of users
@ -380,11 +406,11 @@ impl GroupSessionManager {
message_index: u32, message_index: u32,
being_shared: Arc<DashMap<Uuid, OutboundGroupSession>>, being_shared: Arc<DashMap<Uuid, OutboundGroupSession>>,
) -> OlmResult<Vec<Session>> { ) -> OlmResult<Vec<Session>> {
let (id, request, used_sessions) = let (id, request, share_infos, used_sessions) =
Self::encrypt_session_for(content.clone(), chunk).await?; Self::encrypt_session_for(content.clone(), chunk, message_index).await?;
if !request.messages.is_empty() { if !request.messages.is_empty() {
outbound.add_request(id, request.into(), message_index); outbound.add_request(id, request.into(), share_infos);
being_shared.insert(id, outbound.clone()); being_shared.insert(id, outbound.clone());
} }
@ -453,12 +479,8 @@ impl GroupSessionManager {
let devices: Vec<Device> = devices let devices: Vec<Device> = devices
.into_iter() .into_iter()
.map(|(_, d)| { .map(|(_, d)| {
d.into_iter().filter(|d| { d.into_iter()
matches!( .filter(|d| matches!(outbound.is_shared_with(d), ShareState::NotShared))
outbound.is_shared_with(d.user_id(), d.device_id()),
ShareState::NotShared
)
})
}) })
.flatten() .flatten()
.collect(); .collect();

View file

@ -28,7 +28,7 @@ use tracing::{error, info, warn};
use crate::{ use crate::{
error::OlmResult, error::OlmResult,
key_request::KeyRequestMachine, gossiping::GossipMachine,
olm::Account, olm::Account,
requests::{OutgoingRequest, ToDeviceRequest}, requests::{OutgoingRequest, ToDeviceRequest},
store::{Changes, Result as StoreResult, Store}, store::{Changes, Result as StoreResult, Store},
@ -45,7 +45,7 @@ pub(crate) struct SessionManager {
/// [`get_missing_sessions`](#method.get_missing_sessions) is called. /// [`get_missing_sessions`](#method.get_missing_sessions) is called.
users_for_key_claim: Arc<DashMap<UserId, DashSet<DeviceIdBox>>>, users_for_key_claim: Arc<DashMap<UserId, DashSet<DeviceIdBox>>>,
wedged_devices: Arc<DashMap<UserId, DashSet<DeviceIdBox>>>, wedged_devices: Arc<DashMap<UserId, DashSet<DeviceIdBox>>>,
key_request_machine: KeyRequestMachine, key_request_machine: GossipMachine,
outgoing_to_device_requests: Arc<DashMap<Uuid, OutgoingRequest>>, outgoing_to_device_requests: Arc<DashMap<Uuid, OutgoingRequest>>,
} }
@ -56,7 +56,7 @@ impl SessionManager {
pub fn new( pub fn new(
account: Account, account: Account,
users_for_key_claim: Arc<DashMap<UserId, DashSet<DeviceIdBox>>>, users_for_key_claim: Arc<DashMap<UserId, DashSet<DeviceIdBox>>>,
key_request_machine: KeyRequestMachine, key_request_machine: GossipMachine,
store: Store, store: Store,
) -> Self { ) -> Self {
Self { Self {
@ -279,7 +279,22 @@ impl SessionManager {
} }
} }
Ok(self.store.save_changes(changes).await?) // TODO turn this into a single save_changes() call.
self.store.save_changes(changes).await?;
match self.key_request_machine.collect_incoming_key_requests().await {
Ok(sessions) => {
let changes = Changes { sessions, ..Default::default() };
self.store.save_changes(changes).await?
}
// We don't propagate the error here since the next sync will retry
// this.
Err(e) => {
warn!(error =? e, "Error while trying to collect the incoming secret requests")
}
}
Ok(())
} }
} }
@ -297,8 +312,8 @@ mod test {
use super::SessionManager; use super::SessionManager;
use crate::{ use crate::{
gossiping::GossipMachine,
identities::ReadOnlyDevice, identities::ReadOnlyDevice,
key_request::KeyRequestMachine,
olm::{Account, PrivateCrossSigningIdentity, ReadOnlyAccount}, olm::{Account, PrivateCrossSigningIdentity, ReadOnlyAccount},
session_manager::GroupSessionCache, session_manager::GroupSessionCache,
store::{CryptoStore, MemoryStore, Store}, store::{CryptoStore, MemoryStore, Store},
@ -338,7 +353,7 @@ mod test {
let session_cache = GroupSessionCache::new(store.clone()); let session_cache = GroupSessionCache::new(store.clone());
let key_request = KeyRequestMachine::new( let key_request = GossipMachine::new(
user_id, user_id,
device_id, device_id,
store.clone(), store.clone(),

View file

@ -19,20 +19,25 @@ use std::{
use dashmap::{DashMap, DashSet}; use dashmap::{DashMap, DashSet};
use matrix_sdk_common::{async_trait, locks::Mutex, uuid::Uuid}; use matrix_sdk_common::{async_trait, locks::Mutex, uuid::Uuid};
use ruma::{events::room_key_request::RequestedKeyInfo, DeviceId, DeviceIdBox, RoomId, UserId}; use ruma::{DeviceId, DeviceIdBox, RoomId, UserId};
use super::{ use super::{
caches::{DeviceStore, GroupSessionStore, SessionStore}, caches::{DeviceStore, GroupSessionStore, SessionStore},
Changes, CryptoStore, InboundGroupSession, ReadOnlyAccount, Result, Session, Changes, CryptoStore, InboundGroupSession, ReadOnlyAccount, Result, Session,
}; };
use crate::{ use crate::{
gossiping::{GossipRequest, SecretInfo},
identities::{ReadOnlyDevice, ReadOnlyUserIdentities}, identities::{ReadOnlyDevice, ReadOnlyUserIdentities},
key_request::OutgoingKeyRequest,
olm::{OutboundGroupSession, PrivateCrossSigningIdentity}, olm::{OutboundGroupSession, PrivateCrossSigningIdentity},
}; };
fn encode_key_info(info: &RequestedKeyInfo) -> String { fn encode_key_info(info: &SecretInfo) -> String {
format!("{}{}{}{}", info.room_id, info.sender_key, info.algorithm, info.session_id) match info {
SecretInfo::KeyRequest(info) => {
format!("{}{}{}{}", info.room_id, info.sender_key, info.algorithm, info.session_id)
}
SecretInfo::SecretRequest(i) => i.as_ref().to_owned(),
}
} }
/// An in-memory only store that will forget all the E2EE key once it's dropped. /// An in-memory only store that will forget all the E2EE key once it's dropped.
@ -45,7 +50,7 @@ pub struct MemoryStore {
olm_hashes: Arc<DashMap<String, DashSet<String>>>, olm_hashes: Arc<DashMap<String, DashSet<String>>>,
devices: DeviceStore, devices: DeviceStore,
identities: Arc<DashMap<UserId, ReadOnlyUserIdentities>>, identities: Arc<DashMap<UserId, ReadOnlyUserIdentities>>,
outgoing_key_requests: Arc<DashMap<Uuid, OutgoingKeyRequest>>, outgoing_key_requests: Arc<DashMap<Uuid, GossipRequest>>,
key_requests_by_info: Arc<DashMap<String, Uuid>>, key_requests_by_info: Arc<DashMap<String, Uuid>>,
} }
@ -228,17 +233,17 @@ impl CryptoStore for MemoryStore {
.contains(&message_hash.hash)) .contains(&message_hash.hash))
} }
async fn get_outgoing_key_request( async fn get_outgoing_secret_requests(
&self, &self,
request_id: Uuid, request_id: Uuid,
) -> Result<Option<OutgoingKeyRequest>> { ) -> Result<Option<GossipRequest>> {
Ok(self.outgoing_key_requests.get(&request_id).map(|r| r.clone())) Ok(self.outgoing_key_requests.get(&request_id).map(|r| r.clone()))
} }
async fn get_key_request_by_info( async fn get_secret_request_by_info(
&self, &self,
key_info: &RequestedKeyInfo, key_info: &SecretInfo,
) -> Result<Option<OutgoingKeyRequest>> { ) -> Result<Option<GossipRequest>> {
let key_info_string = encode_key_info(key_info); let key_info_string = encode_key_info(key_info);
Ok(self Ok(self
@ -247,7 +252,7 @@ impl CryptoStore for MemoryStore {
.and_then(|i| self.outgoing_key_requests.get(&i).map(|r| r.clone()))) .and_then(|i| self.outgoing_key_requests.get(&i).map(|r| r.clone())))
} }
async fn get_unsent_key_requests(&self) -> Result<Vec<OutgoingKeyRequest>> { async fn get_unsent_secret_requests(&self) -> Result<Vec<GossipRequest>> {
Ok(self Ok(self
.outgoing_key_requests .outgoing_key_requests
.iter() .iter()
@ -256,7 +261,7 @@ impl CryptoStore for MemoryStore {
.collect()) .collect())
} }
async fn delete_outgoing_key_request(&self, request_id: Uuid) -> Result<()> { async fn delete_outgoing_secret_requests(&self, request_id: Uuid) -> Result<()> {
self.outgoing_key_requests.remove(&request_id).and_then(|(_, i)| { self.outgoing_key_requests.remove(&request_id).and_then(|(_, i)| {
let key_info_string = encode_key_info(&i.info); let key_info_string = encode_key_info(&i.info);
self.key_requests_by_info.remove(&key_info_string) self.key_requests_by_info.remove(&key_info_string)

View file

@ -51,31 +51,35 @@ use std::{
sync::Arc, sync::Arc,
}; };
use base64::DecodeError;
use matrix_sdk_common::{async_trait, locks::Mutex, uuid::Uuid, AsyncTraitDeps}; use matrix_sdk_common::{async_trait, locks::Mutex, uuid::Uuid, AsyncTraitDeps};
pub use memorystore::MemoryStore; pub use memorystore::MemoryStore;
use olm_rs::errors::{OlmAccountError, OlmGroupSessionError, OlmSessionError}; use olm_rs::errors::{OlmAccountError, OlmGroupSessionError, OlmSessionError};
pub use pickle_key::{EncryptedPickleKey, PickleKey}; pub use pickle_key::{EncryptedPickleKey, PickleKey};
use ruma::{ use ruma::{
events::room_key_request::RequestedKeyInfo, identifiers::Error as IdentifierValidationError, events::secret::request::SecretName, identifiers::Error as IdentifierValidationError, DeviceId,
DeviceId, DeviceIdBox, DeviceKeyAlgorithm, RoomId, UserId, DeviceIdBox, DeviceKeyAlgorithm, RoomId, UserId,
}; };
use serde_json::Error as SerdeError; use serde_json::Error as SerdeError;
use thiserror::Error; use thiserror::Error;
use tracing::{info, warn};
use zeroize::Zeroize;
#[cfg(feature = "sled_cryptostore")] #[cfg(feature = "sled_cryptostore")]
pub use self::sled::SledStore; pub use self::sled::SledStore;
use crate::{ use crate::{
error::SessionUnpicklingError, error::SessionUnpicklingError,
gossiping::{GossipRequest, SecretInfo},
identities::{ identities::{
user::{OwnUserIdentity, UserIdentities, UserIdentity}, user::{OwnUserIdentity, UserIdentities, UserIdentity},
Device, ReadOnlyDevice, ReadOnlyUserIdentities, UserDevices, Device, ReadOnlyDevice, ReadOnlyUserIdentities, UserDevices,
}, },
key_request::OutgoingKeyRequest,
olm::{ olm::{
InboundGroupSession, OlmMessageHash, OutboundGroupSession, PrivateCrossSigningIdentity, InboundGroupSession, OlmMessageHash, OutboundGroupSession, PrivateCrossSigningIdentity,
ReadOnlyAccount, Session, ReadOnlyAccount, Session,
}, },
verification::VerificationMachine, verification::VerificationMachine,
CrossSigningStatus,
}; };
/// A `CryptoStore` specific result type. /// A `CryptoStore` specific result type.
@ -105,7 +109,7 @@ pub struct Changes {
pub inbound_group_sessions: Vec<InboundGroupSession>, pub inbound_group_sessions: Vec<InboundGroupSession>,
pub outbound_group_sessions: Vec<OutboundGroupSession>, pub outbound_group_sessions: Vec<OutboundGroupSession>,
pub identities: IdentityChanges, pub identities: IdentityChanges,
pub key_requests: Vec<OutgoingKeyRequest>, pub key_requests: Vec<GossipRequest>,
pub devices: DeviceChanges, pub devices: DeviceChanges,
} }
@ -133,6 +137,48 @@ impl DeviceChanges {
} }
} }
/// A struct containing private cross signing keys that can be backed up or
/// uploaded to the secret store.
#[derive(Zeroize)]
#[zeroize(drop)]
pub struct CrossSigningKeyExport {
/// The seed of the master key encoded as unpadded base64.
pub master_key: Option<String>,
/// The seed of the self signing key encoded as unpadded base64.
pub self_signing_key: Option<String>,
/// The seed of the user signing key encoded as unpadded base64.
pub user_signing_key: Option<String>,
}
impl Debug for CrossSigningKeyExport {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("CrossSigningKeyExport")
.field("master_key", &self.master_key.is_some())
.field("self_signing_key", &self.self_signing_key.is_some())
.field("user_signing_key", &self.user_signing_key.is_some())
.finish_non_exhaustive()
}
}
/// Error describing what went wrong when importing private cross signing keys
/// or the key backup key.
#[derive(Debug, Error)]
pub enum SecretImportError {
/// The seed for the private key wasn't valid base64.
#[error(transparent)]
Base64(#[from] DecodeError),
/// The public key of the imported private key doesn't match to the public
/// key that was uploaded to the server.
#[error(
"The public key of the imported private key doesn't match to the \
public key that was uploaded to the server"
)]
MissmatchedPublicKeys,
/// The new version of the identity couldn't be stored.
#[error(transparent)]
Store(#[from] CryptoStoreError),
}
impl Store { impl Store {
pub fn new( pub fn new(
user_id: Arc<UserId>, user_id: Arc<UserId>,
@ -143,12 +189,21 @@ impl Store {
Self { user_id, identity, inner: store, verification_machine } Self { user_id, identity, inner: store, verification_machine }
} }
pub async fn get_readonly_device( pub fn user_id(&self) -> &UserId {
&self, &self.user_id
user_id: &UserId, }
device_id: &DeviceId,
) -> Result<Option<ReadOnlyDevice>> { pub fn device_id(&self) -> &DeviceId {
self.inner.get_device(user_id, device_id).await self.verification_machine.own_device_id()
}
#[cfg(test)]
pub async fn reset_cross_signing_identity(&self) {
self.identity.lock().await.reset().await;
}
pub fn private_identity(&self) -> Arc<Mutex<PrivateCrossSigningIdentity>> {
self.identity.clone()
} }
pub async fn save_sessions(&self, sessions: &[Session]) -> Result<()> { pub async fn save_sessions(&self, sessions: &[Session]) -> Result<()> {
@ -177,13 +232,58 @@ impl Store {
self.save_changes(changes).await self.save_changes(changes).await
} }
/// Get the display name of our own device.
pub async fn device_display_name(&self) -> Result<Option<String>, CryptoStoreError> {
Ok(self
.inner
.get_device(self.user_id(), self.device_id())
.await?
.and_then(|d| d.display_name().to_owned()))
}
/// Get the read-only version of all the devices that the given user has.
///
/// *Note*: This doesn't return our own device.
pub async fn get_readonly_device(
&self,
user_id: &UserId,
device_id: &DeviceId,
) -> Result<Option<ReadOnlyDevice>> {
if user_id == self.user_id() && device_id == self.device_id() {
Ok(None)
} else {
self.inner.get_device(user_id, device_id).await
}
}
/// Get the read-only version of all the devices that the given user has.
///
/// *Note*: This doesn't return our own device.
pub async fn get_readonly_devices( pub async fn get_readonly_devices(
&self, &self,
user_id: &UserId, user_id: &UserId,
) -> Result<HashMap<DeviceIdBox, ReadOnlyDevice>> {
self.inner.get_user_devices(user_id).await.map(|mut d| {
if user_id == self.user_id() {
d.remove(self.device_id());
}
d
})
}
/// Get the read-only version of all the devices that the given user has.
///
/// *Note*: This does also return our own device.
pub async fn get_readonly_devices_unfiltered(
&self,
user_id: &UserId,
) -> Result<HashMap<DeviceIdBox, ReadOnlyDevice>> { ) -> Result<HashMap<DeviceIdBox, ReadOnlyDevice>> {
self.inner.get_user_devices(user_id).await self.inner.get_user_devices(user_id).await
} }
/// Get a device for the given user with the given curve25519 key.
///
/// *Note*: This doesn't return our own device.
pub async fn get_device_from_curve_key( pub async fn get_device_from_curve_key(
&self, &self,
user_id: &UserId, user_id: &UserId,
@ -197,7 +297,11 @@ impl Store {
} }
pub async fn get_user_devices(&self, user_id: &UserId) -> Result<UserDevices> { pub async fn get_user_devices(&self, user_id: &UserId) -> Result<UserDevices> {
let devices = self.inner.get_user_devices(user_id).await?; let mut devices = self.inner.get_user_devices(user_id).await?;
if user_id == self.user_id() {
devices.remove(self.device_id());
}
let own_identity = let own_identity =
self.inner.get_user_identity(&self.user_id).await?.map(|i| i.own().cloned()).flatten(); self.inner.get_user_identity(&self.user_id).await?.map(|i| i.own().cloned()).flatten();
@ -217,31 +321,145 @@ impl Store {
user_id: &UserId, user_id: &UserId,
device_id: &DeviceId, device_id: &DeviceId,
) -> Result<Option<Device>> { ) -> Result<Option<Device>> {
let own_identity = if user_id == self.user_id() && device_id == self.device_id() {
self.inner.get_user_identity(&self.user_id).await?.map(|i| i.own().cloned()).flatten(); Ok(None)
let device_owner_identity = self.inner.get_user_identity(user_id).await?; } else {
let own_identity = self
.inner
.get_user_identity(&self.user_id)
.await?
.map(|i| i.own().cloned())
.flatten();
let device_owner_identity = self.inner.get_user_identity(user_id).await?;
Ok(self.inner.get_device(user_id, device_id).await?.map(|d| Device { Ok(self.inner.get_device(user_id, device_id).await?.map(|d| Device {
inner: d, inner: d,
private_identity: self.identity.clone(), verification_machine: self.verification_machine.clone(),
verification_machine: self.verification_machine.clone(), own_identity,
own_identity, device_owner_identity,
device_owner_identity, }))
})) }
} }
pub async fn get_identity(&self, user_id: &UserId) -> Result<Option<UserIdentities>> { pub async fn get_identity(&self, user_id: &UserId) -> Result<Option<UserIdentities>> {
Ok(self.inner.get_user_identity(user_id).await?.map(|i| match i { // let own_identity =
ReadOnlyUserIdentities::Own(i) => OwnUserIdentity { // self.inner.get_user_identity(self.user_id()).await?.and_then(|i| i.own());
inner: i, Ok(if let Some(identity) = self.inner.get_user_identity(user_id).await? {
verification_machine: self.verification_machine.clone(), Some(match identity {
} ReadOnlyUserIdentities::Own(i) => OwnUserIdentity {
.into(), inner: i,
ReadOnlyUserIdentities::Other(i) => { verification_machine: self.verification_machine.clone(),
UserIdentity { inner: i, verification_machine: self.verification_machine.clone() } }
.into(),
ReadOnlyUserIdentities::Other(i) => {
let own_identity =
self.inner.get_user_identity(self.user_id()).await?.and_then(|i| {
if let ReadOnlyUserIdentities::Own(i) = i {
Some(i)
} else {
None
}
});
UserIdentity {
inner: i,
verification_machine: self.verification_machine.clone(),
own_identity,
}
.into() .into()
}
})
} else {
None
})
}
/// Try to export the secret with the given secret name.
///
/// The exported secret will be encoded as unpadded base64. Returns `Null`
/// if the secret can't be found.
///
/// # Arguments
///
/// * `secret_name` - The name of the secret that should be exported.
pub async fn export_secret(&self, secret_name: &SecretName) -> Option<String> {
match secret_name {
SecretName::CrossSigningMasterKey
| SecretName::CrossSigningUserSigningKey
| SecretName::CrossSigningSelfSigningKey => {
self.identity.lock().await.export_secret(secret_name).await
} }
})) SecretName::RecoveryKey => None,
name => {
warn!(secret =? name, "Unknown secret was requested");
None
}
}
}
pub async fn import_cross_signing_keys(
&self,
export: CrossSigningKeyExport,
) -> Result<CrossSigningStatus, SecretImportError> {
if let Some(public_identity) = self.get_identity(&self.user_id).await?.and_then(|i| i.own())
{
let identity = self.identity.lock().await;
identity
.import_secrets(
public_identity,
export.master_key.as_deref(),
export.self_signing_key.as_deref(),
export.user_signing_key.as_deref(),
)
.await?;
let status = identity.status().await;
info!(status =? status, "Successfully imported the private cross signing keys");
let changes =
Changes { private_identity: Some(identity.clone()), ..Default::default() };
self.save_changes(changes).await?;
}
Ok(self.identity.lock().await.status().await)
}
pub async fn import_secret(
&self,
secret_name: &SecretName,
secret: String,
) -> Result<(), SecretImportError> {
let secret = zeroize::Zeroizing::new(secret);
match secret_name {
SecretName::CrossSigningMasterKey
| SecretName::CrossSigningUserSigningKey
| SecretName::CrossSigningSelfSigningKey => {
if let Some(public_identity) =
self.get_identity(&self.user_id).await?.and_then(|i| i.own())
{
let identity = self.identity.lock().await;
identity.import_secret(public_identity, secret_name, &secret).await?;
info!(
secret_name = secret_name.as_ref(),
"Successfully imported a private cross signing key"
);
let changes =
Changes { private_identity: Some(identity.clone()), ..Default::default() };
self.save_changes(changes).await?;
}
}
SecretName::RecoveryKey => (),
name => {
warn!(secret =? name, "Tried to import an unknown secret");
}
}
Ok(())
} }
} }
@ -410,31 +628,29 @@ pub trait CryptoStore: AsyncTraitDeps {
/// Check if a hash for an Olm message stored in the database. /// Check if a hash for an Olm message stored in the database.
async fn is_message_known(&self, message_hash: &OlmMessageHash) -> Result<bool>; async fn is_message_known(&self, message_hash: &OlmMessageHash) -> Result<bool>;
/// Get an outgoing key request that we created that matches the given /// Get an outgoing secret request that we created that matches the given
/// request id. /// request id.
/// ///
/// # Arguments /// # Arguments
/// ///
/// * `request_id` - The unique request id that identifies this outgoing key /// * `request_id` - The unique request id that identifies this outgoing
/// request. /// secret request.
async fn get_outgoing_key_request( async fn get_outgoing_secret_requests(&self, request_id: Uuid)
&self, -> Result<Option<GossipRequest>>;
request_id: Uuid,
) -> Result<Option<OutgoingKeyRequest>>;
/// Get an outgoing key request that we created that matches the given /// Get an outgoing key request that we created that matches the given
/// requested key info. /// requested key info.
/// ///
/// # Arguments /// # Arguments
/// ///
/// * `key_info` - The key info of an outgoing key request. /// * `key_info` - The key info of an outgoing secret request.
async fn get_key_request_by_info( async fn get_secret_request_by_info(
&self, &self,
key_info: &RequestedKeyInfo, secret_info: &SecretInfo,
) -> Result<Option<OutgoingKeyRequest>>; ) -> Result<Option<GossipRequest>>;
/// Get all outgoing key requests that we have in the store. /// Get all outgoing secret requests that we have in the store.
async fn get_unsent_key_requests(&self) -> Result<Vec<OutgoingKeyRequest>>; async fn get_unsent_secret_requests(&self) -> Result<Vec<GossipRequest>>;
/// Delete an outgoing key request that we created that matches the given /// Delete an outgoing key request that we created that matches the given
/// request id. /// request id.
@ -443,5 +659,5 @@ pub trait CryptoStore: AsyncTraitDeps {
/// ///
/// * `request_id` - The unique request id that identifies this outgoing key /// * `request_id` - The unique request id that identifies this outgoing key
/// request. /// request.
async fn delete_outgoing_key_request(&self, request_id: Uuid) -> Result<()>; async fn delete_outgoing_secret_requests(&self, request_id: Uuid) -> Result<()>;
} }

View file

@ -14,7 +14,7 @@
use std::{ use std::{
collections::{HashMap, HashSet}, collections::{HashMap, HashSet},
convert::TryFrom, convert::{TryFrom, TryInto},
path::{Path, PathBuf}, path::{Path, PathBuf},
sync::{Arc, RwLock}, sync::{Arc, RwLock},
}; };
@ -22,12 +22,16 @@ use std::{
use dashmap::DashSet; use dashmap::DashSet;
use matrix_sdk_common::{async_trait, locks::Mutex, uuid}; use matrix_sdk_common::{async_trait, locks::Mutex, uuid};
use olm_rs::{account::IdentityKeys, PicklingMode}; use olm_rs::{account::IdentityKeys, PicklingMode};
use ruma::{events::room_key_request::RequestedKeyInfo, DeviceId, DeviceIdBox, RoomId, UserId}; use ruma::{
events::{room_key_request::RequestedKeyInfo, secret::request::SecretName},
DeviceId, DeviceIdBox, RoomId, UserId,
};
pub use sled::Error; pub use sled::Error;
use sled::{ use sled::{
transaction::{ConflictableTransactionError, TransactionError}, transaction::{ConflictableTransactionError, TransactionError},
Config, Db, Transactional, Tree, Config, Db, Transactional, Tree,
}; };
use tracing::trace;
use uuid::Uuid; use uuid::Uuid;
use super::{ use super::{
@ -35,14 +39,15 @@ use super::{
ReadOnlyAccount, Result, Session, ReadOnlyAccount, Result, Session,
}; };
use crate::{ use crate::{
gossiping::{GossipRequest, SecretInfo},
identities::{ReadOnlyDevice, ReadOnlyUserIdentities}, identities::{ReadOnlyDevice, ReadOnlyUserIdentities},
key_request::OutgoingKeyRequest,
olm::{OutboundGroupSession, PickledInboundGroupSession, PrivateCrossSigningIdentity}, olm::{OutboundGroupSession, PickledInboundGroupSession, PrivateCrossSigningIdentity},
}; };
/// This needs to be 32 bytes long since AES-GCM requires it, otherwise we will /// This needs to be 32 bytes long since AES-GCM requires it, otherwise we will
/// panic once we try to pickle a Signing object. /// panic once we try to pickle a Signing object.
const DEFAULT_PICKLE: &str = "DEFAULT_PICKLE_PASSPHRASE_123456"; const DEFAULT_PICKLE: &str = "DEFAULT_PICKLE_PASSPHRASE_123456";
const DATABASE_VERSION: u8 = 1;
trait EncodeKey { trait EncodeKey {
const SEPARATOR: u8 = 0xff; const SEPARATOR: u8 = 0xff;
@ -55,6 +60,21 @@ impl EncodeKey for Uuid {
} }
} }
impl EncodeKey for SecretName {
fn encode(&self) -> Vec<u8> {
[self.as_ref().as_bytes(), &[Self::SEPARATOR]].concat()
}
}
impl EncodeKey for SecretInfo {
fn encode(&self) -> Vec<u8> {
match self {
SecretInfo::KeyRequest(k) => k.encode(),
SecretInfo::SecretRequest(s) => s.encode(),
}
}
}
impl EncodeKey for &RequestedKeyInfo { impl EncodeKey for &RequestedKeyInfo {
fn encode(&self) -> Vec<u8> { fn encode(&self) -> Vec<u8> {
[ [
@ -136,9 +156,9 @@ pub struct SledStore {
inbound_group_sessions: Tree, inbound_group_sessions: Tree,
outbound_group_sessions: Tree, outbound_group_sessions: Tree,
outgoing_key_requests: Tree, outgoing_secret_requests: Tree,
unsent_key_requests: Tree, unsent_secret_requests: Tree,
key_requests_by_info: Tree, secret_requests_by_info: Tree,
devices: Tree, devices: Tree,
identities: Tree, identities: Tree,
@ -186,12 +206,43 @@ impl SledStore {
self.account_info.read().unwrap().clone() self.account_info.read().unwrap().clone()
} }
fn upgrade_databse(db: &Db) -> Result<()> {
let version = db
.get("version")?
.map(|v| {
let (version_bytes, _) = v.split_at(std::mem::size_of::<u8>());
u8::from_be_bytes(version_bytes.try_into().unwrap_or_default())
})
.unwrap_or_default();
if version != DATABASE_VERSION {
trace!(
version = version,
new_version = DATABASE_VERSION,
"Upgrading the Sled crypto store"
);
}
if version == 0 {
// We changed the schema but migrating this isn't important since we
// rotate the group sessions relatively often anyways so we just
// drop it.
db.drop_tree("outbound_group_sessions")?;
}
db.insert("version", DATABASE_VERSION.to_be_bytes().as_ref())?;
Ok(())
}
fn open_helper(db: Db, path: Option<PathBuf>, passphrase: Option<&str>) -> Result<Self> { fn open_helper(db: Db, path: Option<PathBuf>, passphrase: Option<&str>) -> Result<Self> {
Self::upgrade_databse(&db)?;
let account = db.open_tree("account")?; let account = db.open_tree("account")?;
let private_identity = db.open_tree("private_identity")?; let private_identity = db.open_tree("private_identity")?;
let sessions = db.open_tree("session")?; let sessions = db.open_tree("session")?;
let inbound_group_sessions = db.open_tree("inbound_group_sessions")?; let inbound_group_sessions = db.open_tree("inbound_group_sessions")?;
let outbound_group_sessions = db.open_tree("outbound_group_sessions")?; let outbound_group_sessions = db.open_tree("outbound_group_sessions")?;
let tracked_users = db.open_tree("tracked_users")?; let tracked_users = db.open_tree("tracked_users")?;
@ -201,9 +252,9 @@ impl SledStore {
let devices = db.open_tree("devices")?; let devices = db.open_tree("devices")?;
let identities = db.open_tree("identities")?; let identities = db.open_tree("identities")?;
let outgoing_key_requests = db.open_tree("outgoing_key_requests")?; let outgoing_secret_requests = db.open_tree("outgoing_secret_requests")?;
let unsent_key_requests = db.open_tree("unsent_key_requests")?; let unsent_secret_requests = db.open_tree("unsent_secret_requests")?;
let key_requests_by_info = db.open_tree("key_requests_by_info")?; let secret_requests_by_info = db.open_tree("secret_requests_by_info")?;
let session_cache = SessionStore::new(); let session_cache = SessionStore::new();
@ -227,9 +278,9 @@ impl SledStore {
users_for_key_query_cache: DashSet::new().into(), users_for_key_query_cache: DashSet::new().into(),
inbound_group_sessions, inbound_group_sessions,
outbound_group_sessions, outbound_group_sessions,
outgoing_key_requests, outgoing_secret_requests,
unsent_key_requests, unsent_secret_requests,
key_requests_by_info, secret_requests_by_info,
devices, devices,
tracked_users, tracked_users,
users_for_key_query, users_for_key_query,
@ -308,7 +359,7 @@ impl SledStore {
}; };
let private_identity_pickle = if let Some(i) = changes.private_identity { let private_identity_pickle = if let Some(i) = changes.private_identity {
Some(i.pickle(DEFAULT_PICKLE.as_bytes()).await?) Some(i.pickle(self.get_pickle_key()).await?)
} else { } else {
None None
}; };
@ -361,9 +412,9 @@ impl SledStore {
&self.inbound_group_sessions, &self.inbound_group_sessions,
&self.outbound_group_sessions, &self.outbound_group_sessions,
&self.olm_hashes, &self.olm_hashes,
&self.outgoing_key_requests, &self.outgoing_secret_requests,
&self.unsent_key_requests, &self.unsent_secret_requests,
&self.key_requests_by_info, &self.secret_requests_by_info,
) )
.transaction( .transaction(
|( |(
@ -375,9 +426,9 @@ impl SledStore {
inbound_sessions, inbound_sessions,
outbound_sessions, outbound_sessions,
hashes, hashes,
outgoing_key_requests, outgoing_secret_requests,
unsent_key_requests, unsent_secret_requests,
key_requests_by_info, secret_requests_by_info,
)| { )| {
if let Some(a) = &account_pickle { if let Some(a) = &account_pickle {
account.insert( account.insert(
@ -446,7 +497,7 @@ impl SledStore {
} }
for key_request in &key_requests { for key_request in &key_requests {
key_requests_by_info.insert( secret_requests_by_info.insert(
(&key_request.info).encode(), (&key_request.info).encode(),
key_request.request_id.encode(), key_request.request_id.encode(),
)?; )?;
@ -454,15 +505,15 @@ impl SledStore {
let key_request_id = key_request.request_id.encode(); let key_request_id = key_request.request_id.encode();
if key_request.sent_out { if key_request.sent_out {
unsent_key_requests.remove(key_request_id.clone())?; unsent_secret_requests.remove(key_request_id.clone())?;
outgoing_key_requests.insert( outgoing_secret_requests.insert(
key_request_id, key_request_id,
serde_json::to_vec(&key_request) serde_json::to_vec(&key_request)
.map_err(ConflictableTransactionError::Abort)?, .map_err(ConflictableTransactionError::Abort)?,
)?; )?;
} else { } else {
outgoing_key_requests.remove(key_request_id.clone())?; outgoing_secret_requests.remove(key_request_id.clone())?;
unsent_key_requests.insert( unsent_secret_requests.insert(
key_request_id, key_request_id,
serde_json::to_vec(&key_request) serde_json::to_vec(&key_request)
.map_err(ConflictableTransactionError::Abort)?, .map_err(ConflictableTransactionError::Abort)?,
@ -480,15 +531,15 @@ impl SledStore {
Ok(()) Ok(())
} }
async fn get_outgoing_key_request_helper( async fn get_outgoing_key_request_helper(&self, id: &[u8]) -> Result<Option<GossipRequest>> {
&self, let request = self
id: &[u8], .outgoing_secret_requests
) -> Result<Option<OutgoingKeyRequest>> { .get(id)?
let request = .map(|r| serde_json::from_slice(&r))
self.outgoing_key_requests.get(id)?.map(|r| serde_json::from_slice(&r)).transpose()?; .transpose()?;
let request = if request.is_none() { let request = if request.is_none() {
self.unsent_key_requests.get(id)?.map(|r| serde_json::from_slice(&r)).transpose()? self.unsent_secret_requests.get(id)?.map(|r| serde_json::from_slice(&r)).transpose()?
} else { } else {
request request
}; };
@ -647,12 +698,7 @@ impl CryptoStore for SledStore {
device_id: &DeviceId, device_id: &DeviceId,
) -> Result<Option<ReadOnlyDevice>> { ) -> Result<Option<ReadOnlyDevice>> {
let key = (user_id.as_str(), device_id.as_str()).encode(); let key = (user_id.as_str(), device_id.as_str()).encode();
Ok(self.devices.get(key)?.map(|d| serde_json::from_slice(&d)).transpose()?)
if let Some(d) = self.devices.get(key)? {
Ok(Some(serde_json::from_slice(&d)?))
} else {
Ok(None)
}
} }
async fn get_user_devices( async fn get_user_devices(
@ -681,20 +727,20 @@ impl CryptoStore for SledStore {
Ok(self.olm_hashes.contains_key(serde_json::to_vec(message_hash)?)?) Ok(self.olm_hashes.contains_key(serde_json::to_vec(message_hash)?)?)
} }
async fn get_outgoing_key_request( async fn get_outgoing_secret_requests(
&self, &self,
request_id: Uuid, request_id: Uuid,
) -> Result<Option<OutgoingKeyRequest>> { ) -> Result<Option<GossipRequest>> {
let request_id = request_id.encode(); let request_id = request_id.encode();
self.get_outgoing_key_request_helper(&request_id).await self.get_outgoing_key_request_helper(&request_id).await
} }
async fn get_key_request_by_info( async fn get_secret_request_by_info(
&self, &self,
key_info: &RequestedKeyInfo, key_info: &SecretInfo,
) -> Result<Option<OutgoingKeyRequest>> { ) -> Result<Option<GossipRequest>> {
let id = self.key_requests_by_info.get(key_info.encode())?; let id = self.secret_requests_by_info.get(key_info.encode())?;
if let Some(id) = id { if let Some(id) = id {
self.get_outgoing_key_request_helper(&id).await self.get_outgoing_key_request_helper(&id).await
@ -703,9 +749,9 @@ impl CryptoStore for SledStore {
} }
} }
async fn get_unsent_key_requests(&self) -> Result<Vec<OutgoingKeyRequest>> { async fn get_unsent_secret_requests(&self) -> Result<Vec<GossipRequest>> {
let requests: Result<Vec<OutgoingKeyRequest>> = self let requests: Result<Vec<GossipRequest>> = self
.unsent_key_requests .unsent_secret_requests
.iter() .iter()
.map(|i| serde_json::from_slice(&i?.1).map_err(CryptoStoreError::from)) .map(|i| serde_json::from_slice(&i?.1).map_err(CryptoStoreError::from))
.collect(); .collect();
@ -713,34 +759,37 @@ impl CryptoStore for SledStore {
requests requests
} }
async fn delete_outgoing_key_request(&self, request_id: Uuid) -> Result<()> { async fn delete_outgoing_secret_requests(&self, request_id: Uuid) -> Result<()> {
let ret: Result<(), TransactionError<serde_json::Error>> = let ret: Result<(), TransactionError<serde_json::Error>> = (
(&self.outgoing_key_requests, &self.unsent_key_requests, &self.key_requests_by_info) &self.outgoing_secret_requests,
.transaction( &self.unsent_secret_requests,
|(outgoing_key_requests, unsent_key_requests, key_requests_by_info)| { &self.secret_requests_by_info,
let sent_request: Option<OutgoingKeyRequest> = outgoing_key_requests )
.remove(request_id.encode())? .transaction(
.map(|r| serde_json::from_slice(&r)) |(outgoing_key_requests, unsent_key_requests, key_requests_by_info)| {
.transpose() let sent_request: Option<GossipRequest> = outgoing_key_requests
.map_err(ConflictableTransactionError::Abort)?; .remove(request_id.encode())?
.map(|r| serde_json::from_slice(&r))
.transpose()
.map_err(ConflictableTransactionError::Abort)?;
let unsent_request: Option<OutgoingKeyRequest> = unsent_key_requests let unsent_request: Option<GossipRequest> = unsent_key_requests
.remove(request_id.encode())? .remove(request_id.encode())?
.map(|r| serde_json::from_slice(&r)) .map(|r| serde_json::from_slice(&r))
.transpose() .transpose()
.map_err(ConflictableTransactionError::Abort)?; .map_err(ConflictableTransactionError::Abort)?;
if let Some(request) = sent_request { if let Some(request) = sent_request {
key_requests_by_info.remove((&request.info).encode())?; key_requests_by_info.remove((&request.info).encode())?;
} }
if let Some(request) = unsent_request { if let Some(request) = unsent_request {
key_requests_by_info.remove((&request.info).encode())?; key_requests_by_info.remove((&request.info).encode())?;
} }
Ok(()) Ok(())
}, },
); );
ret?; ret?;
self.inner.flush_async().await?; self.inner.flush_async().await?;
@ -762,8 +811,9 @@ mod test {
}; };
use tempfile::tempdir; use tempfile::tempdir;
use super::{CryptoStore, OutgoingKeyRequest, SledStore}; use super::{CryptoStore, GossipRequest, SledStore};
use crate::{ use crate::{
gossiping::SecretInfo,
identities::{ identities::{
device::test::get_device, device::test::get_device,
user::test::{get_other_identity, get_own_identity}, user::test::{get_other_identity, get_own_identity},
@ -1199,21 +1249,22 @@ mod test {
let (account, store, _dir) = get_loaded_store().await; let (account, store, _dir) = get_loaded_store().await;
let id = Uuid::new_v4(); let id = Uuid::new_v4();
let info = RequestedKeyInfo::new( let info: SecretInfo = RequestedKeyInfo::new(
EventEncryptionAlgorithm::MegolmV1AesSha2, EventEncryptionAlgorithm::MegolmV1AesSha2,
room_id!("!test:localhost"), room_id!("!test:localhost"),
"test_sender_key".to_string(), "test_sender_key".to_string(),
"test_session_id".to_string(), "test_session_id".to_string(),
); )
.into();
let request = OutgoingKeyRequest { let request = GossipRequest {
request_recipient: account.user_id().to_owned(), request_recipient: account.user_id().to_owned(),
request_id: id, request_id: id,
info: info.clone(), info: info.clone(),
sent_out: false, sent_out: false,
}; };
assert!(store.get_outgoing_key_request(id).await.unwrap().is_none()); assert!(store.get_outgoing_secret_requests(id).await.unwrap().is_none());
let mut changes = Changes::default(); let mut changes = Changes::default();
changes.key_requests.push(request.clone()); changes.key_requests.push(request.clone());
@ -1221,14 +1272,14 @@ mod test {
let request = Some(request); let request = Some(request);
let stored_request = store.get_outgoing_key_request(id).await.unwrap(); let stored_request = store.get_outgoing_secret_requests(id).await.unwrap();
assert_eq!(request, stored_request); assert_eq!(request, stored_request);
let stored_request = store.get_key_request_by_info(&info).await.unwrap(); let stored_request = store.get_secret_request_by_info(&info).await.unwrap();
assert_eq!(request, stored_request); assert_eq!(request, stored_request);
assert!(!store.get_unsent_key_requests().await.unwrap().is_empty()); assert!(!store.get_unsent_secret_requests().await.unwrap().is_empty());
let request = OutgoingKeyRequest { let request = GossipRequest {
request_recipient: account.user_id().to_owned(), request_recipient: account.user_id().to_owned(),
request_id: id, request_id: id,
info: info.clone(), info: info.clone(),
@ -1239,17 +1290,17 @@ mod test {
changes.key_requests.push(request.clone()); changes.key_requests.push(request.clone());
store.save_changes(changes).await.unwrap(); store.save_changes(changes).await.unwrap();
assert!(store.get_unsent_key_requests().await.unwrap().is_empty()); assert!(store.get_unsent_secret_requests().await.unwrap().is_empty());
let stored_request = store.get_outgoing_key_request(id).await.unwrap(); let stored_request = store.get_outgoing_secret_requests(id).await.unwrap();
assert_eq!(Some(request), stored_request); assert_eq!(Some(request), stored_request);
store.delete_outgoing_key_request(id).await.unwrap(); store.delete_outgoing_secret_requests(id).await.unwrap();
let stored_request = store.get_outgoing_key_request(id).await.unwrap(); let stored_request = store.get_outgoing_secret_requests(id).await.unwrap();
assert_eq!(None, stored_request); assert_eq!(None, stored_request);
let stored_request = store.get_key_request_by_info(&info).await.unwrap(); let stored_request = store.get_secret_request_by_info(&info).await.unwrap();
assert_eq!(None, stored_request); assert_eq!(None, stored_request);
assert!(store.get_unsent_key_requests().await.unwrap().is_empty()); assert!(store.get_unsent_secret_requests().await.unwrap().is_empty());
} }
} }

View file

@ -770,6 +770,7 @@ impl TryFrom<OutgoingRequest> for OutgoingContent {
crate::OutgoingRequests::KeysQuery(_) => Err("Invalid request type".to_owned()), crate::OutgoingRequests::KeysQuery(_) => Err("Invalid request type".to_owned()),
crate::OutgoingRequests::ToDeviceRequest(r) => Self::try_from(r.clone()), crate::OutgoingRequests::ToDeviceRequest(r) => Self::try_from(r.clone()),
crate::OutgoingRequests::SignatureUpload(_) => Err("Invalid request type".to_owned()), crate::OutgoingRequests::SignatureUpload(_) => Err("Invalid request type".to_owned()),
crate::OutgoingRequests::KeysClaim(_) => Err("Invalid request type".to_owned()),
crate::OutgoingRequests::RoomMessage(r) => Ok(Self::from(r.clone())), crate::OutgoingRequests::RoomMessage(r) => Ok(Self::from(r.clone())),
} }
} }

View file

@ -34,7 +34,7 @@ use super::{
event_enums::{AnyEvent, AnyVerificationContent, OutgoingContent}, event_enums::{AnyEvent, AnyVerificationContent, OutgoingContent},
requests::VerificationRequest, requests::VerificationRequest,
sas::Sas, sas::Sas,
FlowId, Verification, VerificationResult, FlowId, Verification, VerificationResult, VerificationStore,
}; };
use crate::{ use crate::{
olm::PrivateCrossSigningIdentity, olm::PrivateCrossSigningIdentity,
@ -46,9 +46,8 @@ use crate::{
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct VerificationMachine { pub struct VerificationMachine {
account: ReadOnlyAccount, pub(crate) private_identity: Arc<Mutex<PrivateCrossSigningIdentity>>,
private_identity: Arc<Mutex<PrivateCrossSigningIdentity>>, pub(crate) store: VerificationStore,
pub(crate) store: Arc<dyn CryptoStore>,
verifications: VerificationCache, verifications: VerificationCache,
requests: Arc<DashMap<UserId, DashMap<String, VerificationRequest>>>, requests: Arc<DashMap<UserId, DashMap<String, VerificationRequest>>>,
} }
@ -60,20 +59,19 @@ impl VerificationMachine {
store: Arc<dyn CryptoStore>, store: Arc<dyn CryptoStore>,
) -> Self { ) -> Self {
Self { Self {
account,
private_identity: identity, private_identity: identity,
store, store: VerificationStore { account, inner: store },
verifications: VerificationCache::new(), verifications: VerificationCache::new(),
requests: DashMap::new().into(), requests: DashMap::new().into(),
} }
} }
pub(crate) fn own_user_id(&self) -> &UserId { pub(crate) fn own_user_id(&self) -> &UserId {
self.account.user_id() self.store.account.user_id()
} }
pub(crate) fn own_device_id(&self) -> &DeviceId { pub(crate) fn own_device_id(&self) -> &DeviceId {
self.account.device_id() self.store.account.device_id()
} }
pub(crate) async fn request_to_device_verification( pub(crate) async fn request_to_device_verification(
@ -86,7 +84,6 @@ impl VerificationMachine {
let verification = VerificationRequest::new( let verification = VerificationRequest::new(
self.verifications.clone(), self.verifications.clone(),
self.account.clone(),
self.private_identity.lock().await.clone(), self.private_identity.lock().await.clone(),
self.store.clone(), self.store.clone(),
flow_id, flow_id,
@ -113,7 +110,6 @@ impl VerificationMachine {
let request = VerificationRequest::new( let request = VerificationRequest::new(
self.verifications.clone(), self.verifications.clone(),
self.account.clone(),
self.private_identity.lock().await.clone(), self.private_identity.lock().await.clone(),
self.store.clone(), self.store.clone(),
flow_id, flow_id,
@ -132,13 +128,15 @@ impl VerificationMachine {
device: ReadOnlyDevice, device: ReadOnlyDevice,
) -> Result<(Sas, OutgoingVerificationRequest), CryptoStoreError> { ) -> Result<(Sas, OutgoingVerificationRequest), CryptoStoreError> {
let identity = self.store.get_user_identity(device.user_id()).await?; let identity = self.store.get_user_identity(device.user_id()).await?;
let own_identity =
self.store.get_user_identity(self.own_user_id()).await?.and_then(|i| i.into_own());
let private_identity = self.private_identity.lock().await.clone(); let private_identity = self.private_identity.lock().await.clone();
let (sas, content) = Sas::start( let (sas, content) = Sas::start(
self.account.clone(),
private_identity, private_identity,
device.clone(), device.clone(),
self.store.clone(), self.store.clone(),
own_identity,
identity, identity,
None, None,
true, true,
@ -260,7 +258,7 @@ impl VerificationMachine {
content, content,
))) = request.clone().try_into() ))) = request.clone().try_into()
{ {
let event = ToDeviceEvent { content, sender: self.account.user_id().to_owned() }; let event = ToDeviceEvent { content, sender: self.own_user_id().to_owned() };
events.push(AnyToDeviceEvent::KeyVerificationCancel(event).into()); events.push(AnyToDeviceEvent::KeyVerificationCancel(event).into());
} }
@ -324,8 +322,8 @@ impl VerificationMachine {
}; };
let event_sent_from_us = |event: &AnyEvent<'_>, from_device: &DeviceId| { let event_sent_from_us = |event: &AnyEvent<'_>, from_device: &DeviceId| {
if event.sender() == self.account.user_id() { if event.sender() == self.store.account.user_id() {
from_device == self.account.device_id() || event.is_room_event() from_device == self.store.account.device_id() || event.is_room_event()
} else { } else {
false false
} }
@ -345,7 +343,6 @@ impl VerificationMachine {
if !event_sent_from_us(&event, r.from_device()) { if !event_sent_from_us(&event, r.from_device()) {
let request = VerificationRequest::from_request( let request = VerificationRequest::from_request(
self.verifications.clone(), self.verifications.clone(),
self.account.clone(),
self.private_identity.lock().await.clone(), self.private_identity.lock().await.clone(),
self.store.clone(), self.store.clone(),
event.sender(), event.sender(),
@ -417,15 +414,20 @@ impl VerificationMachine {
self.store.get_device(event.sender(), c.from_device()).await? self.store.get_device(event.sender(), c.from_device()).await?
{ {
let private_identity = self.private_identity.lock().await.clone(); let private_identity = self.private_identity.lock().await.clone();
let own_identity = self
.store
.get_user_identity(self.own_user_id())
.await?
.and_then(|i| i.into_own());
let identity = self.store.get_user_identity(event.sender()).await?; let identity = self.store.get_user_identity(event.sender()).await?;
match Sas::from_start_event( match Sas::from_start_event(
flow_id, flow_id,
c, c,
self.store.clone(), self.store.clone(),
self.account.clone(),
private_identity, private_identity,
device, device,
own_identity,
identity, identity,
None, None,
false, false,
@ -519,10 +521,11 @@ mod test {
use super::{Sas, VerificationMachine}; use super::{Sas, VerificationMachine};
use crate::{ use crate::{
olm::PrivateCrossSigningIdentity, olm::PrivateCrossSigningIdentity,
store::{CryptoStore, MemoryStore}, store::MemoryStore,
verification::{ verification::{
event_enums::{AcceptContent, KeyContent, MacContent, OutgoingContent}, event_enums::{AcceptContent, KeyContent, MacContent, OutgoingContent},
test::wrap_any_to_device_content, test::wrap_any_to_device_content,
VerificationStore,
}, },
ReadOnlyAccount, ReadOnlyDevice, ReadOnlyAccount, ReadOnlyDevice,
}; };
@ -555,16 +558,17 @@ mod test {
store.save_devices(vec![bob_device]).await; store.save_devices(vec![bob_device]).await;
bob_store.save_devices(vec![alice_device.clone()]).await; bob_store.save_devices(vec![alice_device.clone()]).await;
let bob_store: Arc<dyn CryptoStore> = Arc::new(bob_store); let bob_store = VerificationStore { account: bob, inner: Arc::new(bob_store) };
let identity = Arc::new(Mutex::new(PrivateCrossSigningIdentity::empty(alice_id()))); let identity = Arc::new(Mutex::new(PrivateCrossSigningIdentity::empty(alice_id())));
let machine = VerificationMachine::new(alice, identity, Arc::new(store)); let machine = VerificationMachine::new(alice, identity, Arc::new(store));
let (bob_sas, start_content) = Sas::start( let (bob_sas, start_content) = Sas::start(
bob,
PrivateCrossSigningIdentity::empty(bob_id()), PrivateCrossSigningIdentity::empty(bob_id()),
alice_device, alice_device,
bob_store, bob_store,
None, None,
None, None,
None,
true, true,
None, None,
); );

View file

@ -19,10 +19,14 @@ mod qrcode;
mod requests; mod requests;
mod sas; mod sas;
use std::sync::Arc; use std::{
collections::{BTreeMap, HashMap},
sync::Arc,
};
use event_enums::OutgoingContent; use event_enums::OutgoingContent;
pub use machine::VerificationMachine; pub use machine::VerificationMachine;
use matrix_sdk_common::locks::Mutex;
pub use qrcode::QrVerification; pub use qrcode::QrVerification;
pub use requests::VerificationRequest; pub use requests::VerificationRequest;
use ruma::{ use ruma::{
@ -35,18 +39,77 @@ use ruma::{
}, },
AnyMessageEventContent, AnyToDeviceEventContent, AnyMessageEventContent, AnyToDeviceEventContent,
}, },
DeviceId, EventId, RoomId, UserId, DeviceId, DeviceIdBox, DeviceKeyId, EventId, RoomId, UserId,
}; };
pub use sas::{AcceptSettings, Sas}; pub use sas::{AcceptSettings, Sas};
use tracing::{error, info, trace, warn}; use tracing::{error, info, trace, warn};
use crate::{ use crate::{
error::SignatureError, error::SignatureError,
olm::PrivateCrossSigningIdentity, gossiping::{GossipMachine, GossipRequest},
olm::{PrivateCrossSigningIdentity, ReadOnlyAccount, Session},
store::{Changes, CryptoStore}, store::{Changes, CryptoStore},
CryptoStoreError, LocalTrust, ReadOnlyDevice, ReadOnlyUserIdentities, CryptoStoreError, LocalTrust, ReadOnlyDevice, ReadOnlyUserIdentities,
}; };
#[derive(Clone, Debug)]
pub(crate) struct VerificationStore {
pub account: ReadOnlyAccount,
inner: Arc<dyn CryptoStore>,
}
impl VerificationStore {
pub async fn get_device(
&self,
user_id: &UserId,
device_id: &DeviceId,
) -> Result<Option<ReadOnlyDevice>, CryptoStoreError> {
Ok(self.inner.get_device(user_id, device_id).await?.filter(|d| {
!(d.user_id() == self.account.user_id() && d.device_id() == self.account.device_id())
}))
}
pub async fn get_user_identity(
&self,
user_id: &UserId,
) -> Result<Option<ReadOnlyUserIdentities>, CryptoStoreError> {
self.inner.get_user_identity(user_id).await
}
pub async fn save_changes(&self, changes: Changes) -> Result<(), CryptoStoreError> {
self.inner.save_changes(changes).await
}
pub async fn get_user_devices(
&self,
user_id: &UserId,
) -> Result<HashMap<DeviceIdBox, ReadOnlyDevice>, CryptoStoreError> {
self.inner.get_user_devices(user_id).await
}
pub async fn get_sessions(
&self,
sender_key: &str,
) -> Result<Option<Arc<Mutex<Vec<Session>>>>, CryptoStoreError> {
self.inner.get_sessions(sender_key).await
}
/// Get the signatures that have signed our own device.
pub async fn device_signatures(
&self,
) -> Result<Option<BTreeMap<UserId, BTreeMap<DeviceKeyId, String>>>, CryptoStoreError> {
Ok(self
.inner
.get_device(self.account.user_id(), self.account.device_id())
.await?
.map(|d| d.signatures().to_owned()))
}
pub fn inner(&self) -> &dyn CryptoStore {
&*self.inner
}
}
/// An enum over the different verification types the SDK supports. /// An enum over the different verification types the SDK supports.
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub enum Verification { pub enum Verification {
@ -307,7 +370,7 @@ pub enum VerificationResult {
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct IdentitiesBeingVerified { pub struct IdentitiesBeingVerified {
private_identity: PrivateCrossSigningIdentity, private_identity: PrivateCrossSigningIdentity,
store: Arc<dyn CryptoStore>, store: VerificationStore,
device_being_verified: ReadOnlyDevice, device_being_verified: ReadOnlyDevice,
identity_being_verified: Option<ReadOnlyUserIdentities>, identity_being_verified: Option<ReadOnlyUserIdentities>,
} }
@ -343,7 +406,8 @@ impl IdentitiesBeingVerified {
verified_identities: Option<&[ReadOnlyUserIdentities]>, verified_identities: Option<&[ReadOnlyUserIdentities]>,
) -> Result<VerificationResult, CryptoStoreError> { ) -> Result<VerificationResult, CryptoStoreError> {
let device = self.mark_device_as_verified(verified_devices).await?; let device = self.mark_device_as_verified(verified_devices).await?;
let identity = self.mark_identity_as_verified(verified_identities).await?; let (identity, should_request_secrets) =
self.mark_identity_as_verified(verified_identities).await?;
if device.is_none() && identity.is_none() { if device.is_none() && identity.is_none() {
// Something wen't wrong if nothing was verified, we use key // Something wen't wrong if nothing was verified, we use key
@ -361,7 +425,7 @@ impl IdentitiesBeingVerified {
Err(SignatureError::MissingSigningKey) => { Err(SignatureError::MissingSigningKey) => {
warn!( warn!(
"Can't sign the device keys for {} {}, \ "Can't sign the device keys for {} {}, \
no private user signing key found", no private device signing key found",
device.user_id(), device.user_id(),
device.device_id(), device.device_id(),
); );
@ -437,6 +501,11 @@ impl IdentitiesBeingVerified {
identity_signature_request identity_signature_request
}; };
if should_request_secrets {
let secret_requests = self.request_missing_secrets().await;
changes.key_requests = secret_requests;
}
// TODO store the signature upload request as well. // TODO store the signature upload request as well.
self.store.save_changes(changes).await?; self.store.save_changes(changes).await?;
@ -445,19 +514,24 @@ impl IdentitiesBeingVerified {
.unwrap_or(VerificationResult::Ok)) .unwrap_or(VerificationResult::Ok))
} }
async fn request_missing_secrets(&self) -> Vec<GossipRequest> {
let secrets = self.private_identity.get_missing_secrets().await;
GossipMachine::request_missing_secrets(self.user_id(), secrets)
}
async fn mark_identity_as_verified( async fn mark_identity_as_verified(
&self, &self,
verified_identities: Option<&[ReadOnlyUserIdentities]>, verified_identities: Option<&[ReadOnlyUserIdentities]>,
) -> Result<Option<ReadOnlyUserIdentities>, CryptoStoreError> { ) -> Result<(Option<ReadOnlyUserIdentities>, bool), CryptoStoreError> {
// If there wasn't an identity available during the verification flow // If there wasn't an identity available during the verification flow
// return early as there's nothing to do. // return early as there's nothing to do.
if self.identity_being_verified.is_none() { if self.identity_being_verified.is_none() {
return Ok(None); return Ok((None, false));
} }
let identity = self.store.get_user_identity(self.other_user_id()).await?; let identity = self.store.get_user_identity(self.other_user_id()).await?;
if let Some(identity) = identity { Ok(if let Some(identity) = identity {
if self if self
.identity_being_verified .identity_being_verified
.as_ref() .as_ref()
@ -469,11 +543,14 @@ impl IdentitiesBeingVerified {
"Marking the user identity of as verified." "Marking the user identity of as verified."
); );
if let ReadOnlyUserIdentities::Own(i) = &identity { let should_request_secrets = if let ReadOnlyUserIdentities::Own(i) = &identity {
i.mark_as_verified(); i.mark_as_verified();
} true
} else {
false
};
Ok(Some(identity)) (Some(identity), should_request_secrets)
} else { } else {
info!( info!(
user_id = self.other_user_id().as_str(), user_id = self.other_user_id().as_str(),
@ -482,7 +559,7 @@ impl IdentitiesBeingVerified {
the interactive verification", the interactive verification",
); );
Ok(None) (None, false)
} }
} else { } else {
warn!( warn!(
@ -491,7 +568,7 @@ impl IdentitiesBeingVerified {
verification was going on, not marking the identity as verified.", verification was going on, not marking the identity as verified.",
); );
Ok(None) (None, false)
} }
} else { } else {
info!( info!(
@ -499,8 +576,8 @@ impl IdentitiesBeingVerified {
"The identity of the user was deleted while an interactive \ "The identity of the user was deleted while an interactive \
verification was going on.", verification was going on.",
); );
Ok(None) (None, false)
} })
} }
async fn mark_device_as_verified( async fn mark_device_as_verified(

View file

@ -42,12 +42,11 @@ use super::{
event_enums::{CancelContent, DoneContent, OutgoingContent, OwnedStartContent, StartContent}, event_enums::{CancelContent, DoneContent, OutgoingContent, OwnedStartContent, StartContent},
requests::RequestHandle, requests::RequestHandle,
CancelInfo, Cancelled, Done, FlowId, IdentitiesBeingVerified, VerificationResult, CancelInfo, Cancelled, Done, FlowId, IdentitiesBeingVerified, VerificationResult,
VerificationStore,
}; };
use crate::{ use crate::{
olm::{PrivateCrossSigningIdentity, ReadOnlyAccount}, olm::PrivateCrossSigningIdentity, CryptoStoreError, OutgoingVerificationRequest,
store::CryptoStore, ReadOnlyDevice, ReadOnlyUserIdentities, RoomMessageRequest, ToDeviceRequest,
CryptoStoreError, OutgoingVerificationRequest, ReadOnlyDevice, ReadOnlyUserIdentities,
RoomMessageRequest, ToDeviceRequest,
}; };
const SECRET_SIZE: usize = 16; const SECRET_SIZE: usize = 16;
@ -81,7 +80,7 @@ pub enum ScanError {
#[derive(Clone)] #[derive(Clone)]
pub struct QrVerification { pub struct QrVerification {
flow_id: FlowId, flow_id: FlowId,
store: Arc<dyn CryptoStore>, store: VerificationStore,
inner: Arc<QrVerificationData>, inner: Arc<QrVerificationData>,
state: Arc<Mutex<InnerState>>, state: Arc<Mutex<InnerState>>,
identities: IdentitiesBeingVerified, identities: IdentitiesBeingVerified,
@ -430,7 +429,7 @@ impl QrVerification {
} }
pub(crate) fn new_self( pub(crate) fn new_self(
store: Arc<dyn CryptoStore>, store: VerificationStore,
flow_id: FlowId, flow_id: FlowId,
own_master_key: String, own_master_key: String,
other_device_key: String, other_device_key: String,
@ -452,8 +451,7 @@ impl QrVerification {
} }
pub(crate) fn new_self_no_master( pub(crate) fn new_self_no_master(
account: ReadOnlyAccount, store: VerificationStore,
store: Arc<dyn CryptoStore>,
flow_id: FlowId, flow_id: FlowId,
own_master_key: String, own_master_key: String,
identities: IdentitiesBeingVerified, identities: IdentitiesBeingVerified,
@ -464,7 +462,7 @@ impl QrVerification {
let inner: QrVerificationData = SelfVerificationNoMasterKey::new( let inner: QrVerificationData = SelfVerificationNoMasterKey::new(
flow_id.as_str().to_owned(), flow_id.as_str().to_owned(),
account.identity_keys().ed25519().to_string(), store.account.identity_keys().ed25519().to_string(),
own_master_key, own_master_key,
secret, secret,
) )
@ -474,7 +472,7 @@ impl QrVerification {
} }
pub(crate) fn new_cross( pub(crate) fn new_cross(
store: Arc<dyn CryptoStore>, store: VerificationStore,
flow_id: FlowId, flow_id: FlowId,
own_master_key: String, own_master_key: String,
other_master_key: String, other_master_key: String,
@ -498,8 +496,7 @@ impl QrVerification {
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
pub(crate) async fn from_scan( pub(crate) async fn from_scan(
store: Arc<dyn CryptoStore>, store: VerificationStore,
own_account: ReadOnlyAccount,
private_identity: PrivateCrossSigningIdentity, private_identity: PrivateCrossSigningIdentity,
other_user_id: UserId, other_user_id: UserId,
other_device_id: DeviceIdBox, other_device_id: DeviceIdBox,
@ -516,8 +513,8 @@ impl QrVerification {
} }
let own_identity = let own_identity =
store.get_user_identity(own_account.user_id()).await?.ok_or_else(|| { store.get_user_identity(store.account.user_id()).await?.ok_or_else(|| {
ScanError::MissingCrossSigningIdentity(own_account.user_id().to_owned()) ScanError::MissingCrossSigningIdentity(store.account.user_id().to_owned())
})?; })?;
let other_identity = store let other_identity = store
.get_user_identity(&other_user_id) .get_user_identity(&other_user_id)
@ -543,33 +540,23 @@ impl QrVerification {
} }
}; };
let identities = match qr_code { let (device_being_verified, identity_being_verified) = match qr_code {
QrVerificationData::Verification(_) => { QrVerificationData::Verification(_) => {
check_master_key(qr_code.first_key(), &other_identity)?; check_master_key(qr_code.first_key(), &other_identity)?;
check_master_key(qr_code.second_key(), &own_identity)?; check_master_key(qr_code.second_key(), &own_identity)?;
IdentitiesBeingVerified { (other_device, Some(other_identity))
private_identity,
store: store.clone(),
device_being_verified: other_device,
identity_being_verified: Some(other_identity),
}
} }
QrVerificationData::SelfVerification(_) => { QrVerificationData::SelfVerification(_) => {
check_master_key(qr_code.first_key(), &other_identity)?; check_master_key(qr_code.first_key(), &other_identity)?;
if qr_code.second_key() != own_account.identity_keys().ed25519() { if qr_code.second_key() != store.account.identity_keys().ed25519() {
return Err(ScanError::KeyMismatch { return Err(ScanError::KeyMismatch {
expected: own_account.identity_keys().ed25519().to_owned(), expected: store.account.identity_keys().ed25519().to_owned(),
found: qr_code.second_key().to_owned(), found: qr_code.second_key().to_owned(),
}); });
} }
IdentitiesBeingVerified { (other_device, Some(other_identity))
private_identity,
store: store.clone(),
device_being_verified: other_device,
identity_being_verified: Some(other_identity),
}
} }
QrVerificationData::SelfVerificationNoMasterKey(_) => { QrVerificationData::SelfVerificationNoMasterKey(_) => {
let device_key = let device_key =
@ -583,23 +570,26 @@ impl QrVerification {
}); });
} }
check_master_key(qr_code.second_key(), &other_identity)?; check_master_key(qr_code.second_key(), &other_identity)?;
IdentitiesBeingVerified { (other_device, Some(other_identity))
private_identity,
store: store.clone(),
device_being_verified: other_device,
identity_being_verified: None,
}
} }
}; };
let identities = IdentitiesBeingVerified {
private_identity,
store: store.clone(),
device_being_verified,
identity_being_verified,
};
let secret = qr_code.secret().to_owned(); let secret = qr_code.secret().to_owned();
let own_device_id = store.account.device_id().to_owned();
Ok(Self { Ok(Self {
store, store,
flow_id, flow_id,
inner: qr_code.into(), inner: qr_code.into(),
state: Mutex::new(InnerState::Reciprocated(QrState { state: Mutex::new(InnerState::Reciprocated(QrState {
state: Reciprocated { secret, own_device_id: own_account.device_id().to_owned() }, state: Reciprocated { secret, own_device_id },
})) }))
.into(), .into(),
identities, identities,
@ -609,7 +599,7 @@ impl QrVerification {
} }
fn new_helper( fn new_helper(
store: Arc<dyn CryptoStore>, store: VerificationStore,
flow_id: FlowId, flow_id: FlowId,
inner: QrVerificationData, inner: QrVerificationData,
identities: IdentitiesBeingVerified, identities: IdentitiesBeingVerified,
@ -808,7 +798,7 @@ mod test {
store::{Changes, CryptoStore, MemoryStore}, store::{Changes, CryptoStore, MemoryStore},
verification::{ verification::{
event_enums::{DoneContent, OutgoingContent, StartContent}, event_enums::{DoneContent, OutgoingContent, StartContent},
FlowId, IdentitiesBeingVerified, FlowId, IdentitiesBeingVerified, VerificationStore,
}, },
QrVerification, ReadOnlyDevice, QrVerification, ReadOnlyDevice,
}; };
@ -828,8 +818,10 @@ mod test {
#[async_test] #[async_test]
async fn test_verification_creation() { async fn test_verification_creation() {
let store = memory_store(); let store = memory_store();
let account = ReadOnlyAccount::new(&user_id(), &device_id()); let account = ReadOnlyAccount::new(&user_id(), &device_id());
let store = VerificationStore { account: account.clone(), inner: store };
let private_identity = PrivateCrossSigningIdentity::new(user_id()).await; let private_identity = PrivateCrossSigningIdentity::new(user_id()).await;
let flow_id = FlowId::ToDevice("test_transaction".to_owned()); let flow_id = FlowId::ToDevice("test_transaction".to_owned());
@ -847,7 +839,6 @@ mod test {
}; };
let verification = QrVerification::new_self_no_master( let verification = QrVerification::new_self_no_master(
account,
store.clone(), store.clone(),
flow_id.clone(), flow_id.clone(),
master_key.clone(), master_key.clone(),
@ -898,6 +889,8 @@ mod test {
let alice_account = ReadOnlyAccount::new(&user_id(), &device_id()); let alice_account = ReadOnlyAccount::new(&user_id(), &device_id());
let store = memory_store(); let store = memory_store();
let store = VerificationStore { account: alice_account.clone(), inner: store };
let bob_account = ReadOnlyAccount::new(alice_account.user_id(), "BOBDEVICE".into()); let bob_account = ReadOnlyAccount::new(alice_account.user_id(), "BOBDEVICE".into());
let private_identity = PrivateCrossSigningIdentity::new(user_id()).await; let private_identity = PrivateCrossSigningIdentity::new(user_id()).await;
@ -924,7 +917,6 @@ mod test {
}; };
let alice_verification = QrVerification::new_self_no_master( let alice_verification = QrVerification::new_self_no_master(
alice_account.clone(),
store, store,
flow_id.clone(), flow_id.clone(),
master_key.clone(), master_key.clone(),
@ -935,6 +927,8 @@ mod test {
let bob_store = memory_store(); let bob_store = memory_store();
let bob_store = VerificationStore { account: bob_account.clone(), inner: bob_store };
let mut changes = Changes::default(); let mut changes = Changes::default();
changes.identities.new.push(identity.into()); changes.identities.new.push(identity.into());
changes.devices.new.push(alice_device.clone()); changes.devices.new.push(alice_device.clone());
@ -945,7 +939,6 @@ mod test {
let bob_verification = QrVerification::from_scan( let bob_verification = QrVerification::from_scan(
bob_store, bob_store,
bob_account,
private_identity, private_identity,
alice_account.user_id().to_owned(), alice_account.user_id().to_owned(),
alice_account.device_id().to_owned(), alice_account.device_id().to_owned(),

View file

@ -42,13 +42,12 @@ use super::{
CancelContent, DoneContent, OutgoingContent, ReadyContent, RequestContent, StartContent, CancelContent, DoneContent, OutgoingContent, ReadyContent, RequestContent, StartContent,
}, },
qrcode::{QrVerification, ScanError}, qrcode::{QrVerification, ScanError},
CancelInfo, Cancelled, FlowId, IdentitiesBeingVerified, CancelInfo, Cancelled, FlowId, IdentitiesBeingVerified, VerificationStore,
}; };
use crate::{ use crate::{
olm::{PrivateCrossSigningIdentity, ReadOnlyAccount}, olm::{PrivateCrossSigningIdentity, ReadOnlyAccount},
store::CryptoStore, CryptoStoreError, OutgoingVerificationRequest, ReadOnlyDevice, ReadOnlyOwnUserIdentity,
CryptoStoreError, OutgoingVerificationRequest, ReadOnlyDevice, ReadOnlyUserIdentities, ReadOnlyUserIdentities, RoomMessageRequest, Sas, ToDeviceRequest,
RoomMessageRequest, Sas, ToDeviceRequest,
}; };
const SUPPORTED_METHODS: &[VerificationMethod] = &[ const SUPPORTED_METHODS: &[VerificationMethod] = &[
@ -107,16 +106,15 @@ impl VerificationRequest {
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
pub(crate) fn new( pub(crate) fn new(
cache: VerificationCache, cache: VerificationCache,
account: ReadOnlyAccount,
private_cross_signing_identity: PrivateCrossSigningIdentity, private_cross_signing_identity: PrivateCrossSigningIdentity,
store: Arc<dyn CryptoStore>, store: VerificationStore,
flow_id: FlowId, flow_id: FlowId,
other_user: &UserId, other_user: &UserId,
recipient_devices: Vec<DeviceIdBox>, recipient_devices: Vec<DeviceIdBox>,
methods: Option<Vec<VerificationMethod>>, methods: Option<Vec<VerificationMethod>>,
) -> Self { ) -> Self {
let account = store.account.clone();
let inner = Mutex::new(InnerRequest::Created(RequestState::new( let inner = Mutex::new(InnerRequest::Created(RequestState::new(
account.clone(),
private_cross_signing_identity, private_cross_signing_identity,
cache.clone(), cache.clone(),
store, store,
@ -327,7 +325,6 @@ impl VerificationRequest {
if let InnerRequest::Ready(r) = &*state { if let InnerRequest::Ready(r) = &*state {
let qr_verification = QrVerification::from_scan( let qr_verification = QrVerification::from_scan(
r.store.clone(), r.store.clone(),
r.account.clone(),
r.private_cross_signing_identity.clone(), r.private_cross_signing_identity.clone(),
r.other_user_id.clone(), r.other_user_id.clone(),
r.state.other_device_id.clone(), r.state.other_device_id.clone(),
@ -348,17 +345,17 @@ impl VerificationRequest {
pub(crate) fn from_request( pub(crate) fn from_request(
cache: VerificationCache, cache: VerificationCache,
account: ReadOnlyAccount,
private_cross_signing_identity: PrivateCrossSigningIdentity, private_cross_signing_identity: PrivateCrossSigningIdentity,
store: Arc<dyn CryptoStore>, store: VerificationStore,
sender: &UserId, sender: &UserId,
flow_id: FlowId, flow_id: FlowId,
content: &RequestContent, content: &RequestContent,
) -> Self { ) -> Self {
let account = store.account.clone();
Self { Self {
verification_cache: cache.clone(), verification_cache: cache.clone(),
inner: Arc::new(Mutex::new(InnerRequest::Requested(RequestState::from_request_event( inner: Arc::new(Mutex::new(InnerRequest::Requested(RequestState::from_request_event(
account.clone(),
private_cross_signing_identity, private_cross_signing_identity,
cache, cache,
store, store,
@ -471,6 +468,11 @@ impl VerificationRequest {
if self.is_passive() { if self.is_passive() {
None None
} else { } else {
trace!(
other_user = self.other_user().as_str(),
flow_id = self.flow_id().as_str(),
"Timing a verification request out"
);
request request
} }
} else { } else {
@ -573,7 +575,13 @@ impl VerificationRequest {
pub(crate) fn receive_done(&self, sender: &UserId, content: &DoneContent<'_>) { pub(crate) fn receive_done(&self, sender: &UserId, content: &DoneContent<'_>) {
if sender == self.other_user() { if sender == self.other_user() {
let mut inner = self.inner.lock().unwrap().clone(); trace!(
other_user = self.other_user().as_str(),
flow_id = self.flow_id().as_str(),
"Marking a verification request as done"
);
let mut inner = self.inner.lock().unwrap();
inner.receive_done(content); inner.receive_done(content);
} }
} }
@ -610,7 +618,6 @@ impl VerificationRequest {
.clone() .clone()
.start_sas( .start_sas(
s.store.clone(), s.store.clone(),
s.account.clone(),
s.private_cross_signing_identity.clone(), s.private_cross_signing_identity.clone(),
self.we_started, self.we_started,
self.inner.clone().into(), self.inner.clone().into(),
@ -688,16 +695,27 @@ impl InnerRequest {
} }
fn cancel(&mut self, cancelled_by_us: bool, cancel_code: &CancelCode) { fn cancel(&mut self, cancelled_by_us: bool, cancel_code: &CancelCode) {
trace!( let print_info = || {
cancelled_by_us = cancelled_by_us, trace!(
code = cancel_code.as_str(), cancelled_by_us = cancelled_by_us,
"Verification request going into the cancelled state" code = cancel_code.as_str(),
); "Verification request going into the cancelled state"
);
};
*self = InnerRequest::Cancelled(match self { *self = InnerRequest::Cancelled(match self {
InnerRequest::Created(s) => s.clone().into_canceled(cancelled_by_us, cancel_code), InnerRequest::Created(s) => {
InnerRequest::Requested(s) => s.clone().into_canceled(cancelled_by_us, cancel_code), print_info();
InnerRequest::Ready(s) => s.clone().into_canceled(cancelled_by_us, cancel_code), s.clone().into_canceled(cancelled_by_us, cancel_code)
}
InnerRequest::Requested(s) => {
print_info();
s.clone().into_canceled(cancelled_by_us, cancel_code)
}
InnerRequest::Ready(s) => {
print_info();
s.clone().into_canceled(cancelled_by_us, cancel_code)
}
InnerRequest::Passive(_) | InnerRequest::Done(_) | InnerRequest::Cancelled(_) => return, InnerRequest::Passive(_) | InnerRequest::Done(_) | InnerRequest::Cancelled(_) => return,
}); });
} }
@ -720,10 +738,9 @@ impl InnerRequest {
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
struct RequestState<S: Clone> { struct RequestState<S: Clone> {
account: ReadOnlyAccount,
private_cross_signing_identity: PrivateCrossSigningIdentity, private_cross_signing_identity: PrivateCrossSigningIdentity,
verification_cache: VerificationCache, verification_cache: VerificationCache,
store: Arc<dyn CryptoStore>, store: VerificationStore,
flow_id: Arc<FlowId>, flow_id: Arc<FlowId>,
/// The id of the user which is participating in this verification request. /// The id of the user which is participating in this verification request.
@ -736,7 +753,6 @@ struct RequestState<S: Clone> {
impl<S: Clone> RequestState<S> { impl<S: Clone> RequestState<S> {
fn into_done(self, _: &DoneContent) -> RequestState<Done> { fn into_done(self, _: &DoneContent) -> RequestState<Done> {
RequestState::<Done> { RequestState::<Done> {
account: self.account,
private_cross_signing_identity: self.private_cross_signing_identity, private_cross_signing_identity: self.private_cross_signing_identity,
verification_cache: self.verification_cache, verification_cache: self.verification_cache,
store: self.store, store: self.store,
@ -752,7 +768,6 @@ impl<S: Clone> RequestState<S> {
cancel_code: &CancelCode, cancel_code: &CancelCode,
) -> RequestState<Cancelled> { ) -> RequestState<Cancelled> {
RequestState::<Cancelled> { RequestState::<Cancelled> {
account: self.account,
private_cross_signing_identity: self.private_cross_signing_identity, private_cross_signing_identity: self.private_cross_signing_identity,
verification_cache: self.verification_cache, verification_cache: self.verification_cache,
store: self.store, store: self.store,
@ -765,10 +780,9 @@ impl<S: Clone> RequestState<S> {
impl RequestState<Created> { impl RequestState<Created> {
fn new( fn new(
account: ReadOnlyAccount,
private_identity: PrivateCrossSigningIdentity, private_identity: PrivateCrossSigningIdentity,
cache: VerificationCache, cache: VerificationCache,
store: Arc<dyn CryptoStore>, store: VerificationStore,
other_user_id: &UserId, other_user_id: &UserId,
flow_id: &FlowId, flow_id: &FlowId,
methods: Option<Vec<VerificationMethod>>, methods: Option<Vec<VerificationMethod>>,
@ -776,7 +790,6 @@ impl RequestState<Created> {
let our_methods = methods.unwrap_or_else(|| SUPPORTED_METHODS.to_vec()); let our_methods = methods.unwrap_or_else(|| SUPPORTED_METHODS.to_vec());
Self { Self {
account,
other_user_id: other_user_id.to_owned(), other_user_id: other_user_id.to_owned(),
private_cross_signing_identity: private_identity, private_cross_signing_identity: private_identity,
state: Created { our_methods }, state: Created { our_methods },
@ -789,7 +802,6 @@ impl RequestState<Created> {
fn into_ready(self, _sender: &UserId, content: &ReadyContent) -> RequestState<Ready> { fn into_ready(self, _sender: &UserId, content: &ReadyContent) -> RequestState<Ready> {
// TODO check the flow id, and that the methods match what we suggested. // TODO check the flow id, and that the methods match what we suggested.
RequestState { RequestState {
account: self.account,
flow_id: self.flow_id, flow_id: self.flow_id,
verification_cache: self.verification_cache, verification_cache: self.verification_cache,
private_cross_signing_identity: self.private_cross_signing_identity, private_cross_signing_identity: self.private_cross_signing_identity,
@ -821,17 +833,15 @@ struct Requested {
impl RequestState<Requested> { impl RequestState<Requested> {
fn from_request_event( fn from_request_event(
account: ReadOnlyAccount,
private_identity: PrivateCrossSigningIdentity, private_identity: PrivateCrossSigningIdentity,
cache: VerificationCache, cache: VerificationCache,
store: Arc<dyn CryptoStore>, store: VerificationStore,
sender: &UserId, sender: &UserId,
flow_id: &FlowId, flow_id: &FlowId,
content: &RequestContent, content: &RequestContent,
) -> RequestState<Requested> { ) -> RequestState<Requested> {
// TODO only create this if we support the methods // TODO only create this if we support the methods
RequestState { RequestState {
account,
private_cross_signing_identity: private_identity, private_cross_signing_identity: private_identity,
store, store,
verification_cache: cache, verification_cache: cache,
@ -846,7 +856,6 @@ impl RequestState<Requested> {
fn into_passive(self, content: &ReadyContent) -> RequestState<Passive> { fn into_passive(self, content: &ReadyContent) -> RequestState<Passive> {
RequestState { RequestState {
account: self.account,
flow_id: self.flow_id, flow_id: self.flow_id,
verification_cache: self.verification_cache, verification_cache: self.verification_cache,
private_cross_signing_identity: self.private_cross_signing_identity, private_cross_signing_identity: self.private_cross_signing_identity,
@ -858,7 +867,6 @@ impl RequestState<Requested> {
fn accept(self, methods: Vec<VerificationMethod>) -> (RequestState<Ready>, OutgoingContent) { fn accept(self, methods: Vec<VerificationMethod>) -> (RequestState<Ready>, OutgoingContent) {
let state = RequestState { let state = RequestState {
account: self.account.clone(),
store: self.store, store: self.store,
verification_cache: self.verification_cache, verification_cache: self.verification_cache,
private_cross_signing_identity: self.private_cross_signing_identity, private_cross_signing_identity: self.private_cross_signing_identity,
@ -874,7 +882,7 @@ impl RequestState<Requested> {
let content = match self.flow_id.as_ref() { let content = match self.flow_id.as_ref() {
FlowId::ToDevice(i) => { FlowId::ToDevice(i) => {
AnyToDeviceEventContent::KeyVerificationReady(ReadyToDeviceEventContent::new( AnyToDeviceEventContent::KeyVerificationReady(ReadyToDeviceEventContent::new(
self.account.device_id().to_owned(), state.store.account.device_id().to_owned(),
methods, methods,
i.to_owned(), i.to_owned(),
)) ))
@ -883,7 +891,7 @@ impl RequestState<Requested> {
FlowId::InRoom(r, e) => ( FlowId::InRoom(r, e) => (
r.to_owned(), r.to_owned(),
AnyMessageEventContent::KeyVerificationReady(ReadyEventContent::new( AnyMessageEventContent::KeyVerificationReady(ReadyEventContent::new(
self.account.device_id().to_owned(), state.store.account.device_id().to_owned(),
methods, methods,
Relation::new(e.to_owned()), Relation::new(e.to_owned()),
)), )),
@ -912,6 +920,7 @@ impl RequestState<Ready> {
&self, &self,
content: &StartContent<'a>, content: &StartContent<'a>,
other_device: ReadOnlyDevice, other_device: ReadOnlyDevice,
own_identity: Option<ReadOnlyOwnUserIdentity>,
other_identity: Option<ReadOnlyUserIdentities>, other_identity: Option<ReadOnlyUserIdentities>,
we_started: bool, we_started: bool,
request_handle: RequestHandle, request_handle: RequestHandle,
@ -920,9 +929,9 @@ impl RequestState<Ready> {
(&*self.flow_id).to_owned(), (&*self.flow_id).to_owned(),
content, content,
self.store.clone(), self.store.clone(),
self.account.clone(),
self.private_cross_signing_identity.clone(), self.private_cross_signing_identity.clone(),
other_device, other_device,
own_identity,
other_identity, other_identity,
Some(request_handle), Some(request_handle),
we_started, we_started,
@ -991,7 +1000,6 @@ impl RequestState<Ready> {
} }
} else { } else {
Some(QrVerification::new_self_no_master( Some(QrVerification::new_self_no_master(
self.account.clone(),
self.store.clone(), self.store.clone(),
self.flow_id.as_ref().to_owned(), self.flow_id.as_ref().to_owned(),
master_key.to_owned(), master_key.to_owned(),
@ -1094,12 +1102,18 @@ impl RequestState<Ready> {
}; };
let identity = self.store.get_user_identity(sender).await?; let identity = self.store.get_user_identity(sender).await?;
let own_identity = self
.store
.get_user_identity(self.store.account.user_id())
.await?
.and_then(|i| i.into_own());
match content.method() { match content.method() {
StartMethod::SasV1(_) => { StartMethod::SasV1(_) => {
match self.to_started_sas( match self.to_started_sas(
content, content,
device.clone(), device.clone(),
own_identity,
identity, identity,
we_started, we_started,
request_handle, request_handle,
@ -1152,8 +1166,7 @@ impl RequestState<Ready> {
async fn start_sas( async fn start_sas(
self, self,
store: Arc<dyn CryptoStore>, store: VerificationStore,
account: ReadOnlyAccount,
private_identity: PrivateCrossSigningIdentity, private_identity: PrivateCrossSigningIdentity,
we_started: bool, we_started: bool,
request_handle: RequestHandle, request_handle: RequestHandle,
@ -1164,6 +1177,11 @@ impl RequestState<Ready> {
// TODO signal why starting the sas flow doesn't work? // TODO signal why starting the sas flow doesn't work?
let other_identity = store.get_user_identity(&self.other_user_id).await?; let other_identity = store.get_user_identity(&self.other_user_id).await?;
let own_identity = self
.store
.get_user_identity(self.store.account.user_id())
.await?
.and_then(|i| i.into_own());
let device = if let Some(device) = let device = if let Some(device) =
self.store.get_device(&self.other_user_id, &self.state.other_device_id).await? self.store.get_device(&self.other_user_id, &self.state.other_device_id).await?
@ -1182,10 +1200,10 @@ impl RequestState<Ready> {
Ok(Some(match self.flow_id.as_ref() { Ok(Some(match self.flow_id.as_ref() {
FlowId::ToDevice(t) => { FlowId::ToDevice(t) => {
let (sas, content) = Sas::start( let (sas, content) = Sas::start(
account,
private_identity, private_identity,
device, device,
store, store,
own_identity,
other_identity, other_identity,
Some(t.to_owned()), Some(t.to_owned()),
we_started, we_started,
@ -1197,10 +1215,10 @@ impl RequestState<Ready> {
let (sas, content) = Sas::start_in_room( let (sas, content) = Sas::start_in_room(
e.to_owned(), e.to_owned(),
r.to_owned(), r.to_owned(),
account,
private_identity, private_identity,
device, device,
store, store,
own_identity,
other_identity, other_identity,
we_started, we_started,
request_handle, request_handle,
@ -1234,7 +1252,7 @@ mod test {
verification::{ verification::{
cache::VerificationCache, cache::VerificationCache,
event_enums::{OutgoingContent, ReadyContent, RequestContent, StartContent}, event_enums::{OutgoingContent, ReadyContent, RequestContent, StartContent},
FlowId, FlowId, VerificationStore,
}, },
ReadOnlyDevice, ReadOnlyDevice,
}; };
@ -1264,10 +1282,14 @@ mod test {
let alice_store: Box<dyn CryptoStore> = Box::new(MemoryStore::new()); let alice_store: Box<dyn CryptoStore> = Box::new(MemoryStore::new());
let alice_identity = PrivateCrossSigningIdentity::empty(alice_id()); let alice_identity = PrivateCrossSigningIdentity::empty(alice_id());
let alice_store = VerificationStore { account: alice, inner: alice_store.into() };
let bob = ReadOnlyAccount::new(&bob_id(), &bob_device_id()); let bob = ReadOnlyAccount::new(&bob_id(), &bob_device_id());
let bob_store: Box<dyn CryptoStore> = Box::new(MemoryStore::new()); let bob_store: Box<dyn CryptoStore> = Box::new(MemoryStore::new());
let bob_identity = PrivateCrossSigningIdentity::empty(alice_id()); let bob_identity = PrivateCrossSigningIdentity::empty(alice_id());
let bob_store = VerificationStore { account: bob.clone(), inner: bob_store.into() };
let content = let content =
VerificationRequest::request(bob.user_id(), bob.device_id(), &alice_id(), None); VerificationRequest::request(bob.user_id(), bob.device_id(), &alice_id(), None);
@ -1275,9 +1297,8 @@ mod test {
let bob_request = VerificationRequest::new( let bob_request = VerificationRequest::new(
VerificationCache::new(), VerificationCache::new(),
bob,
bob_identity, bob_identity,
bob_store.into(), bob_store,
flow_id.clone(), flow_id.clone(),
&alice_id(), &alice_id(),
vec![], vec![],
@ -1286,9 +1307,8 @@ mod test {
let alice_request = VerificationRequest::from_request( let alice_request = VerificationRequest::from_request(
VerificationCache::new(), VerificationCache::new(),
alice,
alice_identity, alice_identity,
alice_store.into(), alice_store,
&bob_id(), &bob_id(),
flow_id, flow_id,
&(&content).into(), &(&content).into(),
@ -1314,11 +1334,15 @@ mod test {
let alice_store: Box<dyn CryptoStore> = Box::new(MemoryStore::new()); let alice_store: Box<dyn CryptoStore> = Box::new(MemoryStore::new());
let alice_identity = PrivateCrossSigningIdentity::empty(alice_id()); let alice_identity = PrivateCrossSigningIdentity::empty(alice_id());
let alice_store = VerificationStore { account: alice.clone(), inner: alice_store.into() };
let bob = ReadOnlyAccount::new(&bob_id(), &bob_device_id()); let bob = ReadOnlyAccount::new(&bob_id(), &bob_device_id());
let bob_device = ReadOnlyDevice::from_account(&bob).await; let bob_device = ReadOnlyDevice::from_account(&bob).await;
let bob_store: Box<dyn CryptoStore> = Box::new(MemoryStore::new()); let bob_store: Box<dyn CryptoStore> = Box::new(MemoryStore::new());
let bob_identity = PrivateCrossSigningIdentity::empty(alice_id()); let bob_identity = PrivateCrossSigningIdentity::empty(alice_id());
let bob_store = VerificationStore { account: bob.clone(), inner: bob_store.into() };
let mut changes = Changes::default(); let mut changes = Changes::default();
changes.devices.new.push(bob_device.clone()); changes.devices.new.push(bob_device.clone());
alice_store.save_changes(changes).await.unwrap(); alice_store.save_changes(changes).await.unwrap();
@ -1333,9 +1357,8 @@ mod test {
let bob_request = VerificationRequest::new( let bob_request = VerificationRequest::new(
VerificationCache::new(), VerificationCache::new(),
bob,
bob_identity, bob_identity,
bob_store.into(), bob_store,
flow_id.clone(), flow_id.clone(),
&alice_id(), &alice_id(),
vec![], vec![],
@ -1344,9 +1367,8 @@ mod test {
let alice_request = VerificationRequest::from_request( let alice_request = VerificationRequest::from_request(
VerificationCache::new(), VerificationCache::new(),
alice,
alice_identity, alice_identity,
alice_store.into(), alice_store,
&bob_id(), &bob_id(),
flow_id, flow_id,
&(&content).into(), &(&content).into(),
@ -1381,6 +1403,8 @@ mod test {
let alice_store: Box<dyn CryptoStore> = Box::new(MemoryStore::new()); let alice_store: Box<dyn CryptoStore> = Box::new(MemoryStore::new());
let alice_identity = PrivateCrossSigningIdentity::empty(alice_id()); let alice_identity = PrivateCrossSigningIdentity::empty(alice_id());
let alice_store = VerificationStore { account: alice.clone(), inner: alice_store.into() };
let bob = ReadOnlyAccount::new(&bob_id(), &bob_device_id()); let bob = ReadOnlyAccount::new(&bob_id(), &bob_device_id());
let bob_device = ReadOnlyDevice::from_account(&bob).await; let bob_device = ReadOnlyDevice::from_account(&bob).await;
let bob_store: Box<dyn CryptoStore> = Box::new(MemoryStore::new()); let bob_store: Box<dyn CryptoStore> = Box::new(MemoryStore::new());
@ -1394,13 +1418,14 @@ mod test {
changes.devices.new.push(alice_device.clone()); changes.devices.new.push(alice_device.clone());
bob_store.save_changes(changes).await.unwrap(); bob_store.save_changes(changes).await.unwrap();
let bob_store = VerificationStore { account: bob.clone(), inner: bob_store.into() };
let flow_id = FlowId::from("TEST_FLOW_ID".to_owned()); let flow_id = FlowId::from("TEST_FLOW_ID".to_owned());
let bob_request = VerificationRequest::new( let bob_request = VerificationRequest::new(
VerificationCache::new(), VerificationCache::new(),
bob,
bob_identity, bob_identity,
bob_store.into(), bob_store,
flow_id, flow_id,
&alice_id(), &alice_id(),
vec![], vec![],
@ -1414,9 +1439,8 @@ mod test {
let alice_request = VerificationRequest::from_request( let alice_request = VerificationRequest::from_request(
VerificationCache::new(), VerificationCache::new(),
alice,
alice_identity, alice_identity,
alice_store.into(), alice_store,
&bob_id(), &bob_id(),
flow_id, flow_id,
&content, &content,

View file

@ -34,12 +34,13 @@ use crate::{
identities::{ReadOnlyDevice, ReadOnlyUserIdentities}, identities::{ReadOnlyDevice, ReadOnlyUserIdentities},
utilities::encode, utilities::encode,
verification::event_enums::{MacContent, StartContent}, verification::event_enums::{MacContent, StartContent},
ReadOnlyAccount, ReadOnlyAccount, ReadOnlyOwnUserIdentity,
}; };
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct SasIds { pub struct SasIds {
pub account: ReadOnlyAccount, pub account: ReadOnlyAccount,
pub own_identity: Option<ReadOnlyOwnUserIdentity>,
pub other_device: ReadOnlyDevice, pub other_device: ReadOnlyDevice,
pub other_identity: Option<ReadOnlyUserIdentities>, pub other_identity: Option<ReadOnlyUserIdentities>,
} }
@ -304,6 +305,20 @@ pub fn get_mac_content(sas: &OlmSas, ids: &SasIds, flow_id: &FlowId) -> Outgoing
sas.calculate_mac(key, &format!("{}{}", info, key_id)).expect("Can't calculate SAS MAC"), sas.calculate_mac(key, &format!("{}{}", info, key_id)).expect("Can't calculate SAS MAC"),
); );
if let Some(own_identity) = &ids.own_identity {
if own_identity.is_verified() {
if let Some(key) = own_identity.master_key().get_first_key() {
let key_id = format!("{}:{}", DeviceKeyAlgorithm::Ed25519, &key);
let calculated_mac = sas
.calculate_mac(key, &format!("{}{}", info, &key_id))
.expect("Can't calculate SAS Master key MAC");
mac.insert(key_id, calculated_mac);
}
}
}
// TODO Add the cross signing master key here if we trust/have it. // TODO Add the cross signing master key here if we trust/have it.
let mut keys = mac.keys().cloned().collect::<Vec<String>>(); let mut keys = mac.keys().cloned().collect::<Vec<String>>();

View file

@ -34,7 +34,7 @@ use crate::{
event_enums::{AnyVerificationContent, OutgoingContent, OwnedAcceptContent, StartContent}, event_enums::{AnyVerificationContent, OutgoingContent, OwnedAcceptContent, StartContent},
Cancelled, Done, Cancelled, Done,
}, },
ReadOnlyAccount, ReadOnlyAccount, ReadOnlyOwnUserIdentity,
}; };
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
@ -55,10 +55,17 @@ impl InnerSas {
pub fn start( pub fn start(
account: ReadOnlyAccount, account: ReadOnlyAccount,
other_device: ReadOnlyDevice, other_device: ReadOnlyDevice,
own_identity: Option<ReadOnlyOwnUserIdentity>,
other_identity: Option<ReadOnlyUserIdentities>, other_identity: Option<ReadOnlyUserIdentities>,
transaction_id: Option<String>, transaction_id: Option<String>,
) -> (InnerSas, OutgoingContent) { ) -> (InnerSas, OutgoingContent) {
let sas = SasState::<Created>::new(account, other_device, other_identity, transaction_id); let sas = SasState::<Created>::new(
account,
other_device,
own_identity,
other_identity,
transaction_id,
);
let content = sas.as_content(); let content = sas.as_content();
(InnerSas::Created(sas), content.into()) (InnerSas::Created(sas), content.into())
} }
@ -131,6 +138,7 @@ impl InnerSas {
room_id: RoomId, room_id: RoomId,
account: ReadOnlyAccount, account: ReadOnlyAccount,
other_device: ReadOnlyDevice, other_device: ReadOnlyDevice,
own_identity: Option<ReadOnlyOwnUserIdentity>,
other_identity: Option<ReadOnlyUserIdentities>, other_identity: Option<ReadOnlyUserIdentities>,
) -> (InnerSas, OutgoingContent) { ) -> (InnerSas, OutgoingContent) {
let sas = SasState::<Created>::new_in_room( let sas = SasState::<Created>::new_in_room(
@ -138,6 +146,7 @@ impl InnerSas {
event_id, event_id,
account, account,
other_device, other_device,
own_identity,
other_identity, other_identity,
); );
let content = sas.as_content(); let content = sas.as_content();
@ -149,12 +158,14 @@ impl InnerSas {
other_device: ReadOnlyDevice, other_device: ReadOnlyDevice,
flow_id: FlowId, flow_id: FlowId,
content: &StartContent, content: &StartContent,
own_identity: Option<ReadOnlyOwnUserIdentity>,
other_identity: Option<ReadOnlyUserIdentities>, other_identity: Option<ReadOnlyUserIdentities>,
started_from_request: bool, started_from_request: bool,
) -> Result<InnerSas, OutgoingContent> { ) -> Result<InnerSas, OutgoingContent> {
match SasState::<Started>::from_start_event( match SasState::<Started>::from_start_event(
account, account,
other_device, other_device,
own_identity,
other_identity, other_identity,
flow_id, flow_id,
content, content,

View file

@ -35,14 +35,14 @@ use tracing::trace;
use super::{ use super::{
event_enums::{AnyVerificationContent, OutgoingContent, OwnedAcceptContent, StartContent}, event_enums::{AnyVerificationContent, OutgoingContent, OwnedAcceptContent, StartContent},
requests::RequestHandle, requests::RequestHandle,
CancelInfo, FlowId, IdentitiesBeingVerified, VerificationResult, CancelInfo, FlowId, IdentitiesBeingVerified, VerificationResult, VerificationStore,
}; };
use crate::{ use crate::{
identities::{ReadOnlyDevice, ReadOnlyUserIdentities}, identities::{ReadOnlyDevice, ReadOnlyUserIdentities},
olm::PrivateCrossSigningIdentity, olm::PrivateCrossSigningIdentity,
requests::{OutgoingVerificationRequest, RoomMessageRequest}, requests::{OutgoingVerificationRequest, RoomMessageRequest},
store::{CryptoStore, CryptoStoreError}, store::CryptoStoreError,
ReadOnlyAccount, ToDeviceRequest, ReadOnlyAccount, ReadOnlyOwnUserIdentity, ToDeviceRequest,
}; };
/// Short authentication string object. /// Short authentication string object.
@ -143,22 +143,22 @@ impl Sas {
self.inner.lock().unwrap().set_creation_time(time) self.inner.lock().unwrap().set_creation_time(time)
} }
#[allow(clippy::too_many_arguments)]
fn start_helper( fn start_helper(
inner_sas: InnerSas, inner_sas: InnerSas,
account: ReadOnlyAccount,
private_identity: PrivateCrossSigningIdentity, private_identity: PrivateCrossSigningIdentity,
other_device: ReadOnlyDevice, other_device: ReadOnlyDevice,
store: Arc<dyn CryptoStore>, store: VerificationStore,
other_identity: Option<ReadOnlyUserIdentities>, other_identity: Option<ReadOnlyUserIdentities>,
we_started: bool, we_started: bool,
request_handle: Option<RequestHandle>, request_handle: Option<RequestHandle>,
) -> Sas { ) -> Sas {
let flow_id = inner_sas.verification_flow_id(); let flow_id = inner_sas.verification_flow_id();
let account = store.account.clone();
let identities = IdentitiesBeingVerified { let identities = IdentitiesBeingVerified {
private_identity, private_identity,
store: store.clone(), store,
device_being_verified: other_device, device_being_verified: other_device,
identity_being_verified: other_identity, identity_being_verified: other_identity,
}; };
@ -185,18 +185,19 @@ impl Sas {
/// sent out through the server to the other device. /// sent out through the server to the other device.
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
pub(crate) fn start( pub(crate) fn start(
account: ReadOnlyAccount,
private_identity: PrivateCrossSigningIdentity, private_identity: PrivateCrossSigningIdentity,
other_device: ReadOnlyDevice, other_device: ReadOnlyDevice,
store: Arc<dyn CryptoStore>, store: VerificationStore,
own_identity: Option<ReadOnlyOwnUserIdentity>,
other_identity: Option<ReadOnlyUserIdentities>, other_identity: Option<ReadOnlyUserIdentities>,
transaction_id: Option<String>, transaction_id: Option<String>,
we_started: bool, we_started: bool,
request_handle: Option<RequestHandle>, request_handle: Option<RequestHandle>,
) -> (Sas, OutgoingContent) { ) -> (Sas, OutgoingContent) {
let (inner, content) = InnerSas::start( let (inner, content) = InnerSas::start(
account.clone(), store.account.clone(),
other_device.clone(), other_device.clone(),
own_identity,
other_identity.clone(), other_identity.clone(),
transaction_id, transaction_id,
); );
@ -204,7 +205,6 @@ impl Sas {
( (
Self::start_helper( Self::start_helper(
inner, inner,
account,
private_identity, private_identity,
other_device, other_device,
store, store,
@ -230,10 +230,10 @@ impl Sas {
pub(crate) fn start_in_room( pub(crate) fn start_in_room(
flow_id: EventId, flow_id: EventId,
room_id: RoomId, room_id: RoomId,
account: ReadOnlyAccount,
private_identity: PrivateCrossSigningIdentity, private_identity: PrivateCrossSigningIdentity,
other_device: ReadOnlyDevice, other_device: ReadOnlyDevice,
store: Arc<dyn CryptoStore>, store: VerificationStore,
own_identity: Option<ReadOnlyOwnUserIdentity>,
other_identity: Option<ReadOnlyUserIdentities>, other_identity: Option<ReadOnlyUserIdentities>,
we_started: bool, we_started: bool,
request_handle: RequestHandle, request_handle: RequestHandle,
@ -241,15 +241,15 @@ impl Sas {
let (inner, content) = InnerSas::start_in_room( let (inner, content) = InnerSas::start_in_room(
flow_id, flow_id,
room_id, room_id,
account.clone(), store.account.clone(),
other_device.clone(), other_device.clone(),
own_identity,
other_identity.clone(), other_identity.clone(),
); );
( (
Self::start_helper( Self::start_helper(
inner, inner,
account,
private_identity, private_identity,
other_device, other_device,
store, store,
@ -275,26 +275,26 @@ impl Sas {
pub(crate) fn from_start_event( pub(crate) fn from_start_event(
flow_id: FlowId, flow_id: FlowId,
content: &StartContent, content: &StartContent,
store: Arc<dyn CryptoStore>, store: VerificationStore,
account: ReadOnlyAccount,
private_identity: PrivateCrossSigningIdentity, private_identity: PrivateCrossSigningIdentity,
other_device: ReadOnlyDevice, other_device: ReadOnlyDevice,
own_identity: Option<ReadOnlyOwnUserIdentity>,
other_identity: Option<ReadOnlyUserIdentities>, other_identity: Option<ReadOnlyUserIdentities>,
request_handle: Option<RequestHandle>, request_handle: Option<RequestHandle>,
we_started: bool, we_started: bool,
) -> Result<Sas, OutgoingContent> { ) -> Result<Sas, OutgoingContent> {
let inner = InnerSas::from_start_event( let inner = InnerSas::from_start_event(
account.clone(), store.account.clone(),
other_device.clone(), other_device.clone(),
flow_id, flow_id,
content, content,
own_identity,
other_identity.clone(), other_identity.clone(),
request_handle.is_some(), request_handle.is_some(),
)?; )?;
Ok(Self::start_helper( Ok(Self::start_helper(
inner, inner,
account,
private_identity, private_identity,
other_device, other_device,
store, store,
@ -562,9 +562,10 @@ mod test {
use super::Sas; use super::Sas;
use crate::{ use crate::{
olm::PrivateCrossSigningIdentity, olm::PrivateCrossSigningIdentity,
store::{CryptoStore, MemoryStore}, store::MemoryStore,
verification::event_enums::{ verification::{
AcceptContent, KeyContent, MacContent, OutgoingContent, StartContent, event_enums::{AcceptContent, KeyContent, MacContent, OutgoingContent, StartContent},
VerificationStore,
}, },
ReadOnlyAccount, ReadOnlyDevice, ReadOnlyAccount, ReadOnlyDevice,
}; };
@ -593,20 +594,21 @@ mod test {
let bob = ReadOnlyAccount::new(&bob_id(), &bob_device_id()); let bob = ReadOnlyAccount::new(&bob_id(), &bob_device_id());
let bob_device = ReadOnlyDevice::from_account(&bob).await; let bob_device = ReadOnlyDevice::from_account(&bob).await;
let alice_store: Arc<dyn CryptoStore> = Arc::new(MemoryStore::new()); let alice_store =
let bob_store = MemoryStore::new(); VerificationStore { account: alice.clone(), inner: Arc::new(MemoryStore::new()) };
let bob_store = MemoryStore::new();
bob_store.save_devices(vec![alice_device.clone()]).await; bob_store.save_devices(vec![alice_device.clone()]).await;
let bob_store: Arc<dyn CryptoStore> = Arc::new(bob_store); let bob_store = VerificationStore { account: bob.clone(), inner: Arc::new(bob_store) };
let (alice, content) = Sas::start( let (alice, content) = Sas::start(
alice,
PrivateCrossSigningIdentity::empty(alice_id()), PrivateCrossSigningIdentity::empty(alice_id()),
bob_device, bob_device,
alice_store, alice_store,
None, None,
None, None,
None,
true, true,
None, None,
); );
@ -618,11 +620,11 @@ mod test {
flow_id, flow_id,
&content, &content,
bob_store, bob_store,
bob,
PrivateCrossSigningIdentity::empty(bob_id()), PrivateCrossSigningIdentity::empty(bob_id()),
alice_device, alice_device,
None, None,
None, None,
None,
false, false,
) )
.unwrap(); .unwrap();

View file

@ -60,7 +60,7 @@ use crate::{
}, },
Cancelled, Done, FlowId, Cancelled, Done, FlowId,
}, },
ReadOnlyAccount, ReadOnlyAccount, ReadOnlyOwnUserIdentity,
}; };
const KEY_AGREEMENT_PROTOCOLS: &[KeyAgreementProtocol] = const KEY_AGREEMENT_PROTOCOLS: &[KeyAgreementProtocol] =
@ -362,13 +362,21 @@ impl SasState<Created> {
pub fn new( pub fn new(
account: ReadOnlyAccount, account: ReadOnlyAccount,
other_device: ReadOnlyDevice, other_device: ReadOnlyDevice,
own_identity: Option<ReadOnlyOwnUserIdentity>,
other_identity: Option<ReadOnlyUserIdentities>, other_identity: Option<ReadOnlyUserIdentities>,
transaction_id: Option<String>, transaction_id: Option<String>,
) -> SasState<Created> { ) -> SasState<Created> {
let started_from_request = transaction_id.is_some(); let started_from_request = transaction_id.is_some();
let flow_id = let flow_id =
FlowId::ToDevice(transaction_id.unwrap_or_else(|| Uuid::new_v4().to_string())); FlowId::ToDevice(transaction_id.unwrap_or_else(|| Uuid::new_v4().to_string()));
Self::new_helper(flow_id, account, other_device, other_identity, started_from_request) Self::new_helper(
flow_id,
account,
other_device,
own_identity,
other_identity,
started_from_request,
)
} }
/// Create a new SAS in-room verification flow. /// Create a new SAS in-room verification flow.
@ -388,22 +396,24 @@ impl SasState<Created> {
event_id: EventId, event_id: EventId,
account: ReadOnlyAccount, account: ReadOnlyAccount,
other_device: ReadOnlyDevice, other_device: ReadOnlyDevice,
own_identity: Option<ReadOnlyOwnUserIdentity>,
other_identity: Option<ReadOnlyUserIdentities>, other_identity: Option<ReadOnlyUserIdentities>,
) -> SasState<Created> { ) -> SasState<Created> {
let flow_id = FlowId::InRoom(room_id, event_id); let flow_id = FlowId::InRoom(room_id, event_id);
Self::new_helper(flow_id, account, other_device, other_identity, false) Self::new_helper(flow_id, account, other_device, own_identity, other_identity, false)
} }
fn new_helper( fn new_helper(
flow_id: FlowId, flow_id: FlowId,
account: ReadOnlyAccount, account: ReadOnlyAccount,
other_device: ReadOnlyDevice, other_device: ReadOnlyDevice,
own_identity: Option<ReadOnlyOwnUserIdentity>,
other_identity: Option<ReadOnlyUserIdentities>, other_identity: Option<ReadOnlyUserIdentities>,
started_from_request: bool, started_from_request: bool,
) -> SasState<Created> { ) -> SasState<Created> {
SasState { SasState {
inner: Arc::new(Mutex::new(OlmSas::new())), inner: Arc::new(Mutex::new(OlmSas::new())),
ids: SasIds { account, other_device, other_identity }, ids: SasIds { account, other_device, other_identity, own_identity },
verification_flow_id: flow_id.into(), verification_flow_id: flow_id.into(),
creation_time: Arc::new(Instant::now()), creation_time: Arc::new(Instant::now()),
@ -497,6 +507,7 @@ impl SasState<Started> {
pub fn from_start_event( pub fn from_start_event(
account: ReadOnlyAccount, account: ReadOnlyAccount,
other_device: ReadOnlyDevice, other_device: ReadOnlyDevice,
own_identity: Option<ReadOnlyOwnUserIdentity>,
other_identity: Option<ReadOnlyUserIdentities>, other_identity: Option<ReadOnlyUserIdentities>,
flow_id: FlowId, flow_id: FlowId,
content: &StartContent, content: &StartContent,
@ -514,6 +525,7 @@ impl SasState<Started> {
ids: SasIds { ids: SasIds {
account: account.clone(), account: account.clone(),
other_device: other_device.clone(), other_device: other_device.clone(),
own_identity: own_identity.clone(),
other_identity: other_identity.clone(), other_identity: other_identity.clone(),
}, },
@ -536,7 +548,7 @@ impl SasState<Started> {
Ok(SasState { Ok(SasState {
inner: Arc::new(Mutex::new(sas)), inner: Arc::new(Mutex::new(sas)),
ids: SasIds { account, other_device, other_identity }, ids: SasIds { account, other_device, other_identity, own_identity },
creation_time: Arc::new(Instant::now()), creation_time: Arc::new(Instant::now()),
last_event_time: Arc::new(Instant::now()), last_event_time: Arc::new(Instant::now()),
@ -1158,7 +1170,7 @@ mod test {
let bob = ReadOnlyAccount::new(&bob_id(), &bob_device_id()); let bob = ReadOnlyAccount::new(&bob_id(), &bob_device_id());
let bob_device = ReadOnlyDevice::from_account(&bob).await; let bob_device = ReadOnlyDevice::from_account(&bob).await;
let alice_sas = SasState::<Created>::new(alice.clone(), bob_device, None, None); let alice_sas = SasState::<Created>::new(alice.clone(), bob_device, None, None, None);
let start_content = alice_sas.as_content(); let start_content = alice_sas.as_content();
let flow_id = start_content.flow_id(); let flow_id = start_content.flow_id();
@ -1167,6 +1179,7 @@ mod test {
bob.clone(), bob.clone(),
alice_device, alice_device,
None, None,
None,
flow_id, flow_id,
&start_content.as_start_content(), &start_content.as_start_content(),
false, false,
@ -1339,7 +1352,7 @@ mod test {
let bob = ReadOnlyAccount::new(&bob_id(), &bob_device_id()); let bob = ReadOnlyAccount::new(&bob_id(), &bob_device_id());
let bob_device = ReadOnlyDevice::from_account(&bob).await; let bob_device = ReadOnlyDevice::from_account(&bob).await;
let alice_sas = SasState::<Created>::new(alice.clone(), bob_device, None, None); let alice_sas = SasState::<Created>::new(alice.clone(), bob_device, None, None, None);
let mut start_content = alice_sas.as_content(); let mut start_content = alice_sas.as_content();
let method = start_content.method_mut(); let method = start_content.method_mut();
@ -1358,6 +1371,7 @@ mod test {
bob.clone(), bob.clone(),
alice_device.clone(), alice_device.clone(),
None, None,
None,
flow_id, flow_id,
&content, &content,
false, false,
@ -1379,6 +1393,7 @@ mod test {
bob.clone(), bob.clone(),
alice_device, alice_device,
None, None,
None,
flow_id.into(), flow_id.into(),
&content, &content,
false, false,