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();
}
}
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,
}
/// Error type describin different errors that happen when we check or create
/// signatures for a Matrix JSON object.
#[derive(Error, Debug)]
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,
#[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),
/// 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")]
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,
/// 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")]
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")]
NoSignatureFound,
/// The signature couldn't be verified.
#[error("the signature didn't match the provided key")]
VerificationError,
/// The signed object couldn't be deserialized.
#[error(transparent)]
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]);
getrandom(&mut *key).expect("Can't generate randomness");
// Only populate the first 8 bits with randomness, the rest is 0
// initialized.
// Only populate the first 8 bytes with randomness, the rest is 0
// initialized for the counter.
getrandom(&mut iv[0..8]).expect("Can't generate randomness");
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 matrix_sdk_common::locks::Mutex;
use ruma::{
api::client::r0::keys::upload_signatures::Request as SignatureUploadRequest,
encryption::{DeviceKeys, SignedKey},
events::{
forwarded_room_key::ForwardedRoomKeyToDeviceEventContent,
@ -103,7 +104,6 @@ where
/// A device represents a E2EE capable client of an user.
pub struct Device {
pub(crate) inner: ReadOnlyDevice,
pub(crate) private_identity: Arc<Mutex<PrivateCrossSigningIdentity>>,
pub(crate) verification_machine: VerificationMachine,
pub(crate) own_identity: Option<ReadOnlyOwnUserIdentity>,
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)
}
/// 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.
///
/// This won't affect any cross signing trust state, this only sets a flag
@ -232,7 +259,7 @@ impl Device {
&self,
content: AnyToDeviceEventContent,
) -> 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
@ -280,7 +307,6 @@ impl UserDevices {
pub fn get(&self, device_id: &DeviceId) -> Option<Device> {
self.inner.get(device_id).map(|d| Device {
inner: d.clone(),
private_identity: self.private_identity.clone(),
verification_machine: self.verification_machine.clone(),
own_identity: self.own_identity.clone(),
device_owner_identity: self.device_owner_identity.clone(),
@ -302,7 +328,6 @@ impl UserDevices {
pub fn devices(&self) -> impl Iterator<Item = Device> + '_ {
self.inner.values().map(move |d| Device {
inner: d.clone(),
private_identity: self.private_identity.clone(),
verification_machine: self.verification_machine.clone(),
own_identity: self.own_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(
&self,
device_keys: &DeviceKeys,
) -> 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)
}

View file

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

View file

@ -23,7 +23,8 @@ use std::{
};
use ruma::{
encryption::{CrossSigningKey, KeyUsage},
api::client::r0::keys::upload_signatures::Request as SignatureUploadRequest,
encryption::{CrossSigningKey, DeviceKeys, KeyUsage},
events::{
key::verification::VerificationMethod, room::message::KeyVerificationRequestEventContent,
},
@ -31,13 +32,15 @@ use ruma::{
};
use serde::{Deserialize, Serialize};
use serde_json::to_value;
use tracing::error;
use super::{atomic_bool_deserializer, atomic_bool_serializer};
#[cfg(test)]
use crate::olm::PrivateCrossSigningIdentity;
use crate::{
error::SignatureError, olm::Utility, verification::VerificationMachine, CryptoStoreError,
OutgoingVerificationRequest, ReadOnlyDevice, VerificationRequest,
error::SignatureError,
olm::Utility,
store::{Changes, IdentityChanges},
verification::VerificationMachine,
CryptoStoreError, OutgoingVerificationRequest, ReadOnlyDevice, VerificationRequest,
};
/// Enum over the different user identity types we can have.
@ -104,6 +107,27 @@ impl Deref for 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.
pub async fn request_verification(
&self,
@ -124,6 +148,19 @@ impl OwnUserIdentity {
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(
&self,
methods: Option<Vec<VerificationMethod>>,
@ -156,6 +193,7 @@ impl OwnUserIdentity {
#[derive(Debug, Clone)]
pub struct UserIdentity {
pub(crate) inner: ReadOnlyUserIdentity,
pub(crate) own_identity: Option<ReadOnlyOwnUserIdentity>,
pub(crate) verification_machine: VerificationMachine,
}
@ -168,6 +206,38 @@ impl Deref for 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
/// content has been sent out.
pub async fn request_verification(
@ -494,6 +564,22 @@ impl SelfSigningPubkey {
&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.
///
/// # Arguments
@ -503,17 +589,7 @@ impl SelfSigningPubkey {
/// Returns an empty result if the signature check succeeded, otherwise a
/// SignatureError indicating why the check failed.
pub(crate) fn verify_device(&self, device: &ReadOnlyDevice) -> Result<(), SignatureError> {
let (key_id, key) = self.0.keys.iter().next().ok_or(SignatureError::MissingSigningKey)?;
// 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(),
)
self.verify_device_keys(device.as_device_keys())
}
}
@ -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
/// type.
pub fn other(&self) -> Option<&ReadOnlyUserIdentity> {
@ -629,7 +712,7 @@ impl ReadOnlyUserIdentity {
///
/// Returns a `SignatureError` if the self signing key fails to be correctly
/// verified by the given master key.
pub fn new(
pub(crate) fn new(
master_key: MasterPubkey,
self_signing_key: SelfSigningPubkey,
) -> Result<Self, SignatureError> {
@ -639,7 +722,7 @@ impl ReadOnlyUserIdentity {
}
#[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 self_signing_key =
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.
///
/// Returns a `SignatureError` if we failed to update the identity.
pub fn update(
pub(crate) fn update(
&mut self,
master_key: MasterPubkey,
self_signing_key: SelfSigningPubkey,
@ -696,7 +779,7 @@ impl ReadOnlyUserIdentity {
///
/// Returns an empty result if the signature check succeeded, otherwise a
/// SignatureError indicating why the check failed.
pub fn is_device_signed(&self, device: &ReadOnlyDevice) -> Result<(), SignatureError> {
pub(crate) fn is_device_signed(&self, device: &ReadOnlyDevice) -> Result<(), SignatureError> {
if self.user_id() != device.user_id() {
return Err(SignatureError::UserIdMissmatch);
}
@ -739,7 +822,7 @@ impl ReadOnlyOwnUserIdentity {
///
/// Returns a `SignatureError` if the self signing key fails to be correctly
/// verified by the given master key.
pub fn new(
pub(crate) fn new(
master_key: MasterPubkey,
self_signing_key: SelfSigningPubkey,
user_signing_key: UserSigningPubkey,
@ -785,7 +868,7 @@ impl ReadOnlyOwnUserIdentity {
///
/// Returns an empty result if the signature check succeeded, otherwise a
/// SignatureError indicating why the check failed.
pub fn is_identity_signed(
pub(crate) fn is_identity_signed(
&self,
identity: &ReadOnlyUserIdentity,
) -> Result<(), SignatureError> {
@ -804,7 +887,7 @@ impl ReadOnlyOwnUserIdentity {
///
/// Returns an empty result if the signature check succeeded, otherwise a
/// SignatureError indicating why the check failed.
pub fn is_device_signed(&self, device: &ReadOnlyDevice) -> Result<(), SignatureError> {
pub(crate) fn is_device_signed(&self, device: &ReadOnlyDevice) -> Result<(), SignatureError> {
if self.user_id() != device.user_id() {
return Err(SignatureError::UserIdMissmatch);
}
@ -835,7 +918,7 @@ impl ReadOnlyOwnUserIdentity {
/// * `user_signing_key` - The new user signing key of user identity.
///
/// Returns a `SignatureError` if we failed to update the identity.
pub fn update(
pub(crate) fn update(
&mut self,
master_key: MasterPubkey,
self_signing_key: SelfSigningPubkey,
@ -939,14 +1022,13 @@ pub(crate) mod test {
Arc::new(Mutex::new(PrivateCrossSigningIdentity::empty(second.user_id().clone())));
let verification_machine = VerificationMachine::new(
ReadOnlyAccount::new(second.user_id(), second.device_id()),
private_identity.clone(),
private_identity,
Arc::new(MemoryStore::new()),
);
let first = Device {
inner: first,
verification_machine: verification_machine.clone(),
private_identity: private_identity.clone(),
own_identity: Some(identity.clone()),
device_owner_identity: Some(ReadOnlyUserIdentities::Own(identity.clone())),
};
@ -954,7 +1036,6 @@ pub(crate) mod test {
let second = Device {
inner: second,
verification_machine,
private_identity,
own_identity: Some(identity.clone()),
device_owner_identity: Some(ReadOnlyUserIdentities::Own(identity.clone())),
};
@ -991,7 +1072,6 @@ pub(crate) mod test {
let mut device = Device {
inner: device,
verification_machine: verification_machine.clone(),
private_identity: id.clone(),
own_identity: Some(public_identity.clone()),
device_owner_identity: Some(public_identity.clone().into()),
};

View file

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

View file

@ -38,6 +38,7 @@ use ruma::{
EncryptedEventContent, EncryptedEventScheme, EncryptedToDeviceEventContent,
},
room_key::RoomKeyToDeviceEventContent,
secret::request::SecretName,
AnyMessageEventContent, AnyRoomEvent, AnyToDeviceEvent, SyncMessageEvent, ToDeviceEvent,
},
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::{
error::{EventError, MegolmError, MegolmResult, OlmError, OlmResult},
gossiping::GossipMachine,
identities::{user::UserIdentities, Device, IdentityManager, UserDevices},
key_request::KeyRequestMachine,
olm::{
Account, EncryptionSettings, ExportedRoomKey, GroupSessionKey, IdentityKeys,
InboundGroupSession, OlmDecryptionInfo, PrivateCrossSigningIdentity, ReadOnlyAccount,
SessionType,
Account, CrossSigningStatus, EncryptionSettings, ExportedRoomKey, GroupSessionKey,
IdentityKeys, InboundGroupSession, OlmDecryptionInfo, PrivateCrossSigningIdentity,
ReadOnlyAccount, SessionType,
},
requests::{IncomingResponse, OutgoingRequest, UploadSigningKeysRequest},
session_manager::{GroupSessionManager, SessionManager},
store::{
Changes, CryptoStore, DeviceChanges, IdentityChanges, MemoryStore, Result as StoreResult,
Store,
SecretImportError, Store,
},
verification::{Verification, VerificationMachine, VerificationRequest},
ToDeviceRequest,
CrossSigningKeyExport, ToDeviceRequest,
};
/// State machine implementation of the Olm/Megolm encryption protocol used for
@ -93,7 +94,7 @@ pub struct OlmMachine {
verification_machine: VerificationMachine,
/// The state machine that is responsible to handle outgoing and incoming
/// key requests.
key_request_machine: KeyRequestMachine,
key_request_machine: GossipMachine,
/// State machine handling public user identities and devices, keeping track
/// of when a key query needs to be done and handling one.
identity_manager: IdentityManager,
@ -157,7 +158,7 @@ impl OlmMachine {
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(),
device_id.clone(),
store.clone(),
@ -215,12 +216,15 @@ impl OlmMachine {
) -> StoreResult<Self> {
let account = match store.load_account().await? {
Some(a) => {
debug!("Restored account");
debug!(ed25519_key = a.identity_keys().ed25519(), "Restored an Olm account");
a
}
None => {
debug!("Creating a new account");
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?;
account
}
@ -228,7 +232,14 @@ impl OlmMachine {
let identity = match store.load_identity().await? {
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
}
None => {
@ -277,6 +288,11 @@ impl OlmMachine {
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.
///
/// This returns a list of `OutGoingRequest`, those requests need to be sent
@ -696,8 +712,12 @@ impl OlmMachine {
.key_request_machine
.receive_forwarded_room_key(&decrypted.sender_key, &mut e)
.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))
}
}
@ -747,6 +767,9 @@ impl OlmMachine {
AnyToDeviceEvent::RoomKeyRequest(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::KeyVerificationCancel(..)
| AnyToDeviceEvent::KeyVerificationKey(..)
@ -811,19 +834,26 @@ impl OlmMachine {
Ok(e) => e,
Err(e) => {
// Skip invalid events.
warn!("Received an invalid to-device event {:?} {:?}", e, raw_event);
warn!(
error =? e,
"Received an invalid to-device event"
);
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 {
AnyToDeviceEvent::RoomEncrypted(e) => {
let decrypted = match self.decrypt_to_device_event(&e).await {
Ok(e) => e,
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 Err(e) = self
@ -832,8 +862,9 @@ impl OlmMachine {
.await
{
error!(
"Couldn't mark device from {} to be unwedged {:?}",
sender, e
sender = sender.as_str(),
error =? e,
"Couldn't mark device from to be unwedged",
);
}
}
@ -1227,6 +1258,46 @@ impl OlmMachine {
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)]

View file

@ -32,7 +32,7 @@ use olm_rs::{
};
use ruma::{
api::client::r0::keys::{upload_keys, upload_signatures::Request as SignatureUploadRequest},
encryption::{DeviceKeys, OneTimeKey, SignedKey},
encryption::{CrossSigningKey, DeviceKeys, OneTimeKey, SignedKey},
events::{
room::encrypted::{EncryptedEventScheme, EncryptedToDeviceEventContent},
AnyToDeviceEvent, ToDeviceEvent,
@ -52,11 +52,11 @@ use super::{
};
use crate::{
error::{EventError, OlmResult, SessionCreationError},
identities::ReadOnlyDevice,
identities::{MasterPubkey, ReadOnlyDevice},
requests::UploadSigningKeysRequest,
store::{Changes, Store},
utilities::encode,
OlmError,
OlmError, SignatureError,
};
#[derive(Debug, Clone)]
@ -116,13 +116,17 @@ impl Account {
&self,
event: &ToDeviceEvent<EncryptedToDeviceEventContent>,
) -> 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
{
c
} 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());
};
@ -156,6 +160,10 @@ impl Account {
Ok(d) => d,
Err(OlmError::SessionWedged(user_id, sender_key)) => {
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));
} else {
return Err(OlmError::SessionWedged(user_id, sender_key));
@ -164,8 +172,6 @@ impl Account {
Err(e) => return Err(e),
};
debug!("Decrypted a to-device event {:?}", event);
Ok(OlmDecryptionInfo {
session,
message_hash,
@ -176,7 +182,10 @@ impl Account {
inbound_group_session: None,
})
} 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())
}
}
@ -264,9 +273,10 @@ impl Account {
// likely wedged and needs to be rotated.
if matches {
warn!(
"Found a matching Olm session yet decryption failed
for sender {} and sender_key {} {:?}",
sender, sender_key, e
sender = sender.as_str(),
sender_key = sender_key,
error =? e,
"Found a matching Olm session yet decryption failed",
);
return Err(OlmError::SessionWedged(
sender.to_owned(),
@ -302,9 +312,10 @@ impl Account {
// return with an error if it isn't one.
OlmMessage::Message(_) => {
warn!(
sender = sender.as_str(),
sender_key = sender_key,
"Failed to decrypt a non-pre-key message with all \
available sessions {} {}",
sender, sender_key
available sessions",
);
return Err(OlmError::SessionWedged(sender.to_owned(), sender_key.to_owned()));
}
@ -316,9 +327,11 @@ impl Account {
Ok(s) => s,
Err(e) => {
warn!(
"Failed to create a new Olm session for {} {}
from a prekey message: {}",
sender, sender_key, e
sender = sender.as_str(),
sender_key = sender_key,
error =? e,
"Failed to create a new Olm session from a \
prekey message",
);
return Err(OlmError::SessionWedged(
sender.to_owned(),
@ -334,10 +347,26 @@ impl Account {
// Decrypt our message, this shouldn't fail since we're using a
// newly created Session.
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)
};
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) {
Ok(r) => r,
@ -732,6 +761,42 @@ impl ReadOnlyAccount {
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
/// string.
///

View file

@ -60,7 +60,8 @@ pub struct InboundGroupSession {
pub(crate) signing_keys: Arc<BTreeMap<DeviceKeyAlgorithm, String>>,
pub(crate) room_id: Arc<RoomId>,
forwarding_chains: Arc<Vec<String>>,
imported: Arc<bool>,
imported: bool,
backed_up: bool,
}
impl InboundGroupSession {
@ -103,7 +104,8 @@ impl InboundGroupSession {
signing_keys: keys.into(),
room_id: room_id.clone().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(),
room_id: content.room_id.clone().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(),
room_id: (&*self.room_id).clone(),
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(),
}
}
@ -252,7 +256,8 @@ impl InboundGroupSession {
signing_keys: pickle.signing_key.into(),
room_id: pickle.room_id.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
/// or if it was imported.
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.
pub history_visibility: Option<HistoryVisibility>,
}
@ -403,7 +411,8 @@ impl TryFrom<ExportedRoomKey> for InboundGroupSession {
signing_keys: Arc::new(key.sender_claimed_keys),
room_id: Arc::new(key.room_id),
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 outbound::{
EncryptionSettings, OutboundGroupSession, PickledOutboundGroupSession, ShareState,
EncryptionSettings, OutboundGroupSession, PickledOutboundGroupSession, ShareInfo, ShareState,
};
/// The private session key of a group session.

View file

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

View file

@ -27,14 +27,14 @@ pub(crate) use account::{Account, OlmDecryptionInfo, SessionType};
pub use account::{AccountPickle, OlmMessageHash, PickledAccount, ReadOnlyAccount};
pub use group_sessions::{
EncryptionSettings, ExportedRoomKey, InboundGroupSession, InboundGroupSessionPickle,
OutboundGroupSession, PickledInboundGroupSession, PickledOutboundGroupSession,
OutboundGroupSession, PickledInboundGroupSession, PickledOutboundGroupSession, ShareInfo,
};
pub(crate) use group_sessions::{GroupSessionKey, ShareState};
use matrix_sdk_common::instant::{Duration, Instant};
pub use olm_rs::{account::IdentityKeys, PicklingMode};
use serde::{Deserialize, Deserializer, Serialize, Serializer};
pub use session::{PickledSession, Session, SessionPickle};
pub use signing::{PickledCrossSigningIdentity, PrivateCrossSigningIdentity};
pub use signing::{CrossSigningStatus, PickledCrossSigningIdentity, PrivateCrossSigningIdentity};
pub(crate) use utility::Utility;
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();
let event: AnySyncRoomEvent = serde_json::from_str(&event).expect("WHAAAT?!?!?");
let event: AnySyncRoomEvent = serde_json::from_str(&event).unwrap();
let 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::{
api::client::r0::keys::upload_signatures::Request as SignatureUploadRequest,
encryption::{DeviceKeys, KeyUsage},
DeviceKeyAlgorithm, DeviceKeyId, UserId,
events::secret::request::SecretName,
UserId,
};
use serde::{Deserialize, Serialize};
use serde_json::Error as JsonError;
use crate::{
error::SignatureError, identities::MasterPubkey, requests::UploadSigningKeysRequest,
ReadOnlyAccount, ReadOnlyDevice, ReadOnlyOwnUserIdentity, ReadOnlyUserIdentity,
error::SignatureError,
identities::{MasterPubkey, SelfSigningPubkey, UserSigningPubkey},
requests::UploadSigningKeysRequest,
store::SecretImportError,
utilities::decode,
OwnUserIdentity, ReadOnlyAccount, ReadOnlyDevice, ReadOnlyOwnUserIdentity,
ReadOnlyUserIdentity,
};
/// Private cross signing identity.
@ -55,6 +61,26 @@ pub struct PrivateCrossSigningIdentity {
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`.
///
/// Can be used to store the identity.
@ -68,6 +94,20 @@ pub struct PickledCrossSigningIdentity {
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 {
/// Get the user id that this identity belongs to.
pub fn user_id(&self) -> &UserId {
@ -96,16 +136,213 @@ impl PrivateCrossSigningIdentity {
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.
pub async fn has_master_key(&self) -> bool {
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.
pub async fn master_public_key(&self) -> Option<MasterPubkey> {
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.
pub(crate) fn empty(user_id: UserId) -> Self {
Self {
@ -128,6 +365,7 @@ impl PrivateCrossSigningIdentity {
.ok_or(SignatureError::MissingSigningKey)?
.public_key
.clone();
let self_signing = self
.self_signing_key
.lock()
@ -136,6 +374,7 @@ impl PrivateCrossSigningIdentity {
.ok_or(SignatureError::MissingSigningKey)?
.public_key
.clone();
let user_signing = self
.user_signing_key
.lock()
@ -144,6 +383,7 @@ impl PrivateCrossSigningIdentity {
.ok_or(SignatureError::MissingSigningKey)?
.public_key
.clone();
let identity = ReadOnlyOwnUserIdentity::new(master, self_signing, user_signing)?;
identity.mark_as_verified();
@ -155,7 +395,7 @@ impl PrivateCrossSigningIdentity {
&self,
user_identity: &ReadOnlyUserIdentity,
) -> Result<SignatureUploadRequest, SignatureError> {
let signed_keys = self
let master_key = self
.user_signing_key
.lock()
.await
@ -164,6 +404,17 @@ impl PrivateCrossSigningIdentity {
.sign_user(user_identity)
.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))
}
@ -225,22 +476,11 @@ impl PrivateCrossSigningIdentity {
let mut public_key =
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
.signatures
.entry(account.user_id().to_owned())
.or_insert_with(BTreeMap::new)
.insert(
DeviceKeyId::from_parts(DeviceKeyAlgorithm::Ed25519, account.device_id())
.to_string(),
signature,
);
account
.sign_cross_signing_key(&mut public_key)
.await
.expect("Can't sign our freshly created master key with our account");
let master = MasterSigning { inner: master, public_key: public_key.into() };
@ -290,6 +530,12 @@ impl PrivateCrossSigningIdentity {
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.
pub fn mark_as_shared(&self) {
self.shared.store(true, Ordering::SeqCst)
@ -357,23 +603,18 @@ impl PrivateCrossSigningIdentity {
) -> Result<Self, SigningError> {
let signings: PickledSignings = serde_json::from_str(&pickle.pickle)?;
let master = if let Some(m) = signings.master_key {
Some(MasterSigning::from_pickle(m, pickle_key)?)
} else {
None
};
let master =
signings.master_key.map(|m| MasterSigning::from_pickle(m, pickle_key)).transpose()?;
let self_signing = if let Some(s) = signings.self_signing_key {
Some(SelfSigning::from_pickle(s, pickle_key)?)
} else {
None
};
let self_signing = signings
.self_signing_key
.map(|s| SelfSigning::from_pickle(s, pickle_key))
.transpose()?;
let user_signing = if let Some(u) = signings.user_signing_key {
Some(UserSigning::from_pickle(u, pickle_key)?)
} else {
None
};
let user_signing = signings
.user_signing_key
.map(|s| UserSigning::from_pickle(s, pickle_key))
.transpose()?;
Ok(Self {
user_id: Arc::new(pickle.user_id),
@ -388,13 +629,13 @@ impl PrivateCrossSigningIdentity {
/// identity.
pub(crate) async fn as_upload_request(&self) -> UploadSigningKeysRequest {
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 =
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 =
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 }
}
@ -402,10 +643,10 @@ impl PrivateCrossSigningIdentity {
#[cfg(test)]
mod test {
use std::{collections::BTreeMap, sync::Arc};
use std::sync::Arc;
use matrix_sdk_test::async_test;
use ruma::{encryption::CrossSigningKey, user_id, UserId};
use ruma::{user_id, UserId};
use super::{PrivateCrossSigningIdentity, Signing};
use crate::{
@ -525,24 +766,7 @@ mod test {
let user_signing = identity.user_signing_key.lock().await;
let user_signing = user_signing.as_ref().unwrap();
let signatures = 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);
let master = user_signing.sign_user(&bob_public).await.unwrap();
bob_public.master_key = master.into();

View file

@ -24,7 +24,7 @@ use olm_rs::pk::OlmPkSigning;
#[cfg(test)]
use olm_rs::{errors::OlmUtilityError, utility::OlmUtility};
use ruma::{
encryption::{CrossSigningKey, DeviceKeys, KeyUsage},
encryption::{CrossSigningKey, CrossSigningKeySignatures, DeviceKeys, KeyUsage},
serde::CanonicalJsonValue,
DeviceKeyAlgorithm, DeviceKeyId, UserId,
};
@ -36,7 +36,7 @@ use zeroize::Zeroizing;
use crate::{
error::SignatureError,
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,
};
@ -135,6 +135,17 @@ impl MasterSigning {
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(
pickle: PickledMasterSigning,
pickle_key: &[u8],
@ -177,10 +188,33 @@ impl UserSigning {
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(
&self,
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 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(),
)
.to_string(),
serde_json::to_value(signature.0)?,
signature.0,
);
Ok(signatures)
@ -218,6 +252,17 @@ impl SelfSigning {
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> {
self.inner.sign_json(value).await
}
@ -313,9 +358,9 @@ impl Signing {
let key = GenericArray::from_slice(pickle_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 ciphertext = &decode(pickled.ciphertext)?;
let ciphertext = &decode_url_safe(pickled.ciphertext)?;
let seed = cipher
.decrypt(nonce, ciphertext.as_slice())
@ -335,9 +380,10 @@ impl Signing {
let ciphertext =
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"))
}

View file

@ -18,7 +18,7 @@ use matrix_sdk_common::uuid::Uuid;
use ruma::{
api::client::r0::{
keys::{
claim_keys::Response as KeysClaimResponse,
claim_keys::{Request as KeysClaimRequest, Response as KeysClaimResponse},
get_keys::Response as KeysQueryResponse,
upload_keys::{Request as KeysUploadRequest, Response as KeysUploadResponse},
upload_signatures::{
@ -183,6 +183,10 @@ pub enum OutgoingRequests {
/// The keys query request, fetching the device and cross singing keys of
/// other users.
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
/// things, the main use is key requests/forwards and interactive device
/// 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 {
fn from(request: KeysUploadRequest) -> Self {
OutgoingRequests::KeysUpload(request)

View file

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

View file

@ -28,7 +28,7 @@ use tracing::{error, info, warn};
use crate::{
error::OlmResult,
key_request::KeyRequestMachine,
gossiping::GossipMachine,
olm::Account,
requests::{OutgoingRequest, ToDeviceRequest},
store::{Changes, Result as StoreResult, Store},
@ -45,7 +45,7 @@ pub(crate) struct SessionManager {
/// [`get_missing_sessions`](#method.get_missing_sessions) is called.
users_for_key_claim: 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>>,
}
@ -56,7 +56,7 @@ impl SessionManager {
pub fn new(
account: Account,
users_for_key_claim: Arc<DashMap<UserId, DashSet<DeviceIdBox>>>,
key_request_machine: KeyRequestMachine,
key_request_machine: GossipMachine,
store: Store,
) -> 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 crate::{
gossiping::GossipMachine,
identities::ReadOnlyDevice,
key_request::KeyRequestMachine,
olm::{Account, PrivateCrossSigningIdentity, ReadOnlyAccount},
session_manager::GroupSessionCache,
store::{CryptoStore, MemoryStore, Store},
@ -338,7 +353,7 @@ mod test {
let session_cache = GroupSessionCache::new(store.clone());
let key_request = KeyRequestMachine::new(
let key_request = GossipMachine::new(
user_id,
device_id,
store.clone(),

View file

@ -19,20 +19,25 @@ use std::{
use dashmap::{DashMap, DashSet};
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::{
caches::{DeviceStore, GroupSessionStore, SessionStore},
Changes, CryptoStore, InboundGroupSession, ReadOnlyAccount, Result, Session,
};
use crate::{
gossiping::{GossipRequest, SecretInfo},
identities::{ReadOnlyDevice, ReadOnlyUserIdentities},
key_request::OutgoingKeyRequest,
olm::{OutboundGroupSession, PrivateCrossSigningIdentity},
};
fn encode_key_info(info: &RequestedKeyInfo) -> String {
format!("{}{}{}{}", info.room_id, info.sender_key, info.algorithm, info.session_id)
fn encode_key_info(info: &SecretInfo) -> String {
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.
@ -45,7 +50,7 @@ pub struct MemoryStore {
olm_hashes: Arc<DashMap<String, DashSet<String>>>,
devices: DeviceStore,
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>>,
}
@ -228,17 +233,17 @@ impl CryptoStore for MemoryStore {
.contains(&message_hash.hash))
}
async fn get_outgoing_key_request(
async fn get_outgoing_secret_requests(
&self,
request_id: Uuid,
) -> Result<Option<OutgoingKeyRequest>> {
) -> Result<Option<GossipRequest>> {
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,
key_info: &RequestedKeyInfo,
) -> Result<Option<OutgoingKeyRequest>> {
key_info: &SecretInfo,
) -> Result<Option<GossipRequest>> {
let key_info_string = encode_key_info(key_info);
Ok(self
@ -247,7 +252,7 @@ impl CryptoStore for MemoryStore {
.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
.outgoing_key_requests
.iter()
@ -256,7 +261,7 @@ impl CryptoStore for MemoryStore {
.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)| {
let key_info_string = encode_key_info(&i.info);
self.key_requests_by_info.remove(&key_info_string)

View file

@ -51,31 +51,35 @@ use std::{
sync::Arc,
};
use base64::DecodeError;
use matrix_sdk_common::{async_trait, locks::Mutex, uuid::Uuid, AsyncTraitDeps};
pub use memorystore::MemoryStore;
use olm_rs::errors::{OlmAccountError, OlmGroupSessionError, OlmSessionError};
pub use pickle_key::{EncryptedPickleKey, PickleKey};
use ruma::{
events::room_key_request::RequestedKeyInfo, identifiers::Error as IdentifierValidationError,
DeviceId, DeviceIdBox, DeviceKeyAlgorithm, RoomId, UserId,
events::secret::request::SecretName, identifiers::Error as IdentifierValidationError, DeviceId,
DeviceIdBox, DeviceKeyAlgorithm, RoomId, UserId,
};
use serde_json::Error as SerdeError;
use thiserror::Error;
use tracing::{info, warn};
use zeroize::Zeroize;
#[cfg(feature = "sled_cryptostore")]
pub use self::sled::SledStore;
use crate::{
error::SessionUnpicklingError,
gossiping::{GossipRequest, SecretInfo},
identities::{
user::{OwnUserIdentity, UserIdentities, UserIdentity},
Device, ReadOnlyDevice, ReadOnlyUserIdentities, UserDevices,
},
key_request::OutgoingKeyRequest,
olm::{
InboundGroupSession, OlmMessageHash, OutboundGroupSession, PrivateCrossSigningIdentity,
ReadOnlyAccount, Session,
},
verification::VerificationMachine,
CrossSigningStatus,
};
/// A `CryptoStore` specific result type.
@ -105,7 +109,7 @@ pub struct Changes {
pub inbound_group_sessions: Vec<InboundGroupSession>,
pub outbound_group_sessions: Vec<OutboundGroupSession>,
pub identities: IdentityChanges,
pub key_requests: Vec<OutgoingKeyRequest>,
pub key_requests: Vec<GossipRequest>,
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 {
pub fn new(
user_id: Arc<UserId>,
@ -143,12 +189,21 @@ impl Store {
Self { user_id, identity, inner: store, verification_machine }
}
pub async fn get_readonly_device(
&self,
user_id: &UserId,
device_id: &DeviceId,
) -> Result<Option<ReadOnlyDevice>> {
self.inner.get_device(user_id, device_id).await
pub fn user_id(&self) -> &UserId {
&self.user_id
}
pub fn device_id(&self) -> &DeviceId {
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<()> {
@ -177,13 +232,58 @@ impl Store {
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(
&self,
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>> {
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(
&self,
user_id: &UserId,
@ -197,7 +297,11 @@ impl Store {
}
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 =
self.inner.get_user_identity(&self.user_id).await?.map(|i| i.own().cloned()).flatten();
@ -217,31 +321,145 @@ impl Store {
user_id: &UserId,
device_id: &DeviceId,
) -> Result<Option<Device>> {
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?;
if user_id == self.user_id() && device_id == self.device_id() {
Ok(None)
} 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 {
inner: d,
private_identity: self.identity.clone(),
verification_machine: self.verification_machine.clone(),
own_identity,
device_owner_identity,
}))
Ok(self.inner.get_device(user_id, device_id).await?.map(|d| Device {
inner: d,
verification_machine: self.verification_machine.clone(),
own_identity,
device_owner_identity,
}))
}
}
pub async fn get_identity(&self, user_id: &UserId) -> Result<Option<UserIdentities>> {
Ok(self.inner.get_user_identity(user_id).await?.map(|i| match i {
ReadOnlyUserIdentities::Own(i) => OwnUserIdentity {
inner: i,
verification_machine: self.verification_machine.clone(),
}
.into(),
ReadOnlyUserIdentities::Other(i) => {
UserIdentity { inner: i, verification_machine: self.verification_machine.clone() }
// let own_identity =
// self.inner.get_user_identity(self.user_id()).await?.and_then(|i| i.own());
Ok(if let Some(identity) = self.inner.get_user_identity(user_id).await? {
Some(match identity {
ReadOnlyUserIdentities::Own(i) => OwnUserIdentity {
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()
}
})
} 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.
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.
///
/// # Arguments
///
/// * `request_id` - The unique request id that identifies this outgoing key
/// request.
async fn get_outgoing_key_request(
&self,
request_id: Uuid,
) -> Result<Option<OutgoingKeyRequest>>;
/// * `request_id` - The unique request id that identifies this outgoing
/// secret request.
async fn get_outgoing_secret_requests(&self, request_id: Uuid)
-> Result<Option<GossipRequest>>;
/// Get an outgoing key request that we created that matches the given
/// requested key info.
///
/// # Arguments
///
/// * `key_info` - The key info of an outgoing key request.
async fn get_key_request_by_info(
/// * `key_info` - The key info of an outgoing secret request.
async fn get_secret_request_by_info(
&self,
key_info: &RequestedKeyInfo,
) -> Result<Option<OutgoingKeyRequest>>;
secret_info: &SecretInfo,
) -> Result<Option<GossipRequest>>;
/// Get all outgoing key requests that we have in the store.
async fn get_unsent_key_requests(&self) -> Result<Vec<OutgoingKeyRequest>>;
/// Get all outgoing secret requests that we have in the store.
async fn get_unsent_secret_requests(&self) -> Result<Vec<GossipRequest>>;
/// Delete an outgoing key request that we created that matches the given
/// request id.
@ -443,5 +659,5 @@ pub trait CryptoStore: AsyncTraitDeps {
///
/// * `request_id` - The unique request id that identifies this outgoing key
/// 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::{
collections::{HashMap, HashSet},
convert::TryFrom,
convert::{TryFrom, TryInto},
path::{Path, PathBuf},
sync::{Arc, RwLock},
};
@ -22,12 +22,16 @@ use std::{
use dashmap::DashSet;
use matrix_sdk_common::{async_trait, locks::Mutex, uuid};
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;
use sled::{
transaction::{ConflictableTransactionError, TransactionError},
Config, Db, Transactional, Tree,
};
use tracing::trace;
use uuid::Uuid;
use super::{
@ -35,14 +39,15 @@ use super::{
ReadOnlyAccount, Result, Session,
};
use crate::{
gossiping::{GossipRequest, SecretInfo},
identities::{ReadOnlyDevice, ReadOnlyUserIdentities},
key_request::OutgoingKeyRequest,
olm::{OutboundGroupSession, PickledInboundGroupSession, PrivateCrossSigningIdentity},
};
/// This needs to be 32 bytes long since AES-GCM requires it, otherwise we will
/// panic once we try to pickle a Signing object.
const DEFAULT_PICKLE: &str = "DEFAULT_PICKLE_PASSPHRASE_123456";
const DATABASE_VERSION: u8 = 1;
trait EncodeKey {
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 {
fn encode(&self) -> Vec<u8> {
[
@ -136,9 +156,9 @@ pub struct SledStore {
inbound_group_sessions: Tree,
outbound_group_sessions: Tree,
outgoing_key_requests: Tree,
unsent_key_requests: Tree,
key_requests_by_info: Tree,
outgoing_secret_requests: Tree,
unsent_secret_requests: Tree,
secret_requests_by_info: Tree,
devices: Tree,
identities: Tree,
@ -186,12 +206,43 @@ impl SledStore {
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> {
Self::upgrade_databse(&db)?;
let account = db.open_tree("account")?;
let private_identity = db.open_tree("private_identity")?;
let sessions = db.open_tree("session")?;
let inbound_group_sessions = db.open_tree("inbound_group_sessions")?;
let outbound_group_sessions = db.open_tree("outbound_group_sessions")?;
let tracked_users = db.open_tree("tracked_users")?;
@ -201,9 +252,9 @@ impl SledStore {
let devices = db.open_tree("devices")?;
let identities = db.open_tree("identities")?;
let outgoing_key_requests = db.open_tree("outgoing_key_requests")?;
let unsent_key_requests = db.open_tree("unsent_key_requests")?;
let key_requests_by_info = db.open_tree("key_requests_by_info")?;
let outgoing_secret_requests = db.open_tree("outgoing_secret_requests")?;
let unsent_secret_requests = db.open_tree("unsent_secret_requests")?;
let secret_requests_by_info = db.open_tree("secret_requests_by_info")?;
let session_cache = SessionStore::new();
@ -227,9 +278,9 @@ impl SledStore {
users_for_key_query_cache: DashSet::new().into(),
inbound_group_sessions,
outbound_group_sessions,
outgoing_key_requests,
unsent_key_requests,
key_requests_by_info,
outgoing_secret_requests,
unsent_secret_requests,
secret_requests_by_info,
devices,
tracked_users,
users_for_key_query,
@ -308,7 +359,7 @@ impl SledStore {
};
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 {
None
};
@ -361,9 +412,9 @@ impl SledStore {
&self.inbound_group_sessions,
&self.outbound_group_sessions,
&self.olm_hashes,
&self.outgoing_key_requests,
&self.unsent_key_requests,
&self.key_requests_by_info,
&self.outgoing_secret_requests,
&self.unsent_secret_requests,
&self.secret_requests_by_info,
)
.transaction(
|(
@ -375,9 +426,9 @@ impl SledStore {
inbound_sessions,
outbound_sessions,
hashes,
outgoing_key_requests,
unsent_key_requests,
key_requests_by_info,
outgoing_secret_requests,
unsent_secret_requests,
secret_requests_by_info,
)| {
if let Some(a) = &account_pickle {
account.insert(
@ -446,7 +497,7 @@ impl SledStore {
}
for key_request in &key_requests {
key_requests_by_info.insert(
secret_requests_by_info.insert(
(&key_request.info).encode(),
key_request.request_id.encode(),
)?;
@ -454,15 +505,15 @@ impl SledStore {
let key_request_id = key_request.request_id.encode();
if key_request.sent_out {
unsent_key_requests.remove(key_request_id.clone())?;
outgoing_key_requests.insert(
unsent_secret_requests.remove(key_request_id.clone())?;
outgoing_secret_requests.insert(
key_request_id,
serde_json::to_vec(&key_request)
.map_err(ConflictableTransactionError::Abort)?,
)?;
} else {
outgoing_key_requests.remove(key_request_id.clone())?;
unsent_key_requests.insert(
outgoing_secret_requests.remove(key_request_id.clone())?;
unsent_secret_requests.insert(
key_request_id,
serde_json::to_vec(&key_request)
.map_err(ConflictableTransactionError::Abort)?,
@ -480,15 +531,15 @@ impl SledStore {
Ok(())
}
async fn get_outgoing_key_request_helper(
&self,
id: &[u8],
) -> Result<Option<OutgoingKeyRequest>> {
let request =
self.outgoing_key_requests.get(id)?.map(|r| serde_json::from_slice(&r)).transpose()?;
async fn get_outgoing_key_request_helper(&self, id: &[u8]) -> Result<Option<GossipRequest>> {
let request = self
.outgoing_secret_requests
.get(id)?
.map(|r| serde_json::from_slice(&r))
.transpose()?;
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 {
request
};
@ -647,12 +698,7 @@ impl CryptoStore for SledStore {
device_id: &DeviceId,
) -> Result<Option<ReadOnlyDevice>> {
let key = (user_id.as_str(), device_id.as_str()).encode();
if let Some(d) = self.devices.get(key)? {
Ok(Some(serde_json::from_slice(&d)?))
} else {
Ok(None)
}
Ok(self.devices.get(key)?.map(|d| serde_json::from_slice(&d)).transpose()?)
}
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)?)?)
}
async fn get_outgoing_key_request(
async fn get_outgoing_secret_requests(
&self,
request_id: Uuid,
) -> Result<Option<OutgoingKeyRequest>> {
) -> Result<Option<GossipRequest>> {
let request_id = request_id.encode();
self.get_outgoing_key_request_helper(&request_id).await
}
async fn get_key_request_by_info(
async fn get_secret_request_by_info(
&self,
key_info: &RequestedKeyInfo,
) -> Result<Option<OutgoingKeyRequest>> {
let id = self.key_requests_by_info.get(key_info.encode())?;
key_info: &SecretInfo,
) -> Result<Option<GossipRequest>> {
let id = self.secret_requests_by_info.get(key_info.encode())?;
if let Some(id) = id {
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>> {
let requests: Result<Vec<OutgoingKeyRequest>> = self
.unsent_key_requests
async fn get_unsent_secret_requests(&self) -> Result<Vec<GossipRequest>> {
let requests: Result<Vec<GossipRequest>> = self
.unsent_secret_requests
.iter()
.map(|i| serde_json::from_slice(&i?.1).map_err(CryptoStoreError::from))
.collect();
@ -713,34 +759,37 @@ impl CryptoStore for SledStore {
requests
}
async fn delete_outgoing_key_request(&self, request_id: Uuid) -> Result<()> {
let ret: Result<(), TransactionError<serde_json::Error>> =
(&self.outgoing_key_requests, &self.unsent_key_requests, &self.key_requests_by_info)
.transaction(
|(outgoing_key_requests, unsent_key_requests, key_requests_by_info)| {
let sent_request: Option<OutgoingKeyRequest> = outgoing_key_requests
.remove(request_id.encode())?
.map(|r| serde_json::from_slice(&r))
.transpose()
.map_err(ConflictableTransactionError::Abort)?;
async fn delete_outgoing_secret_requests(&self, request_id: Uuid) -> Result<()> {
let ret: Result<(), TransactionError<serde_json::Error>> = (
&self.outgoing_secret_requests,
&self.unsent_secret_requests,
&self.secret_requests_by_info,
)
.transaction(
|(outgoing_key_requests, unsent_key_requests, key_requests_by_info)| {
let sent_request: Option<GossipRequest> = outgoing_key_requests
.remove(request_id.encode())?
.map(|r| serde_json::from_slice(&r))
.transpose()
.map_err(ConflictableTransactionError::Abort)?;
let unsent_request: Option<OutgoingKeyRequest> = unsent_key_requests
.remove(request_id.encode())?
.map(|r| serde_json::from_slice(&r))
.transpose()
.map_err(ConflictableTransactionError::Abort)?;
let unsent_request: Option<GossipRequest> = unsent_key_requests
.remove(request_id.encode())?
.map(|r| serde_json::from_slice(&r))
.transpose()
.map_err(ConflictableTransactionError::Abort)?;
if let Some(request) = sent_request {
key_requests_by_info.remove((&request.info).encode())?;
}
if let Some(request) = sent_request {
key_requests_by_info.remove((&request.info).encode())?;
}
if let Some(request) = unsent_request {
key_requests_by_info.remove((&request.info).encode())?;
}
if let Some(request) = unsent_request {
key_requests_by_info.remove((&request.info).encode())?;
}
Ok(())
},
);
Ok(())
},
);
ret?;
self.inner.flush_async().await?;
@ -762,8 +811,9 @@ mod test {
};
use tempfile::tempdir;
use super::{CryptoStore, OutgoingKeyRequest, SledStore};
use super::{CryptoStore, GossipRequest, SledStore};
use crate::{
gossiping::SecretInfo,
identities::{
device::test::get_device,
user::test::{get_other_identity, get_own_identity},
@ -1199,21 +1249,22 @@ mod test {
let (account, store, _dir) = get_loaded_store().await;
let id = Uuid::new_v4();
let info = RequestedKeyInfo::new(
let info: SecretInfo = RequestedKeyInfo::new(
EventEncryptionAlgorithm::MegolmV1AesSha2,
room_id!("!test:localhost"),
"test_sender_key".to_string(),
"test_session_id".to_string(),
);
)
.into();
let request = OutgoingKeyRequest {
let request = GossipRequest {
request_recipient: account.user_id().to_owned(),
request_id: id,
info: info.clone(),
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();
changes.key_requests.push(request.clone());
@ -1221,14 +1272,14 @@ mod test {
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);
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!(!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_id: id,
info: info.clone(),
@ -1239,17 +1290,17 @@ mod test {
changes.key_requests.push(request.clone());
store.save_changes(changes).await.unwrap();
assert!(store.get_unsent_key_requests().await.unwrap().is_empty());
let stored_request = store.get_outgoing_key_request(id).await.unwrap();
assert!(store.get_unsent_secret_requests().await.unwrap().is_empty());
let stored_request = store.get_outgoing_secret_requests(id).await.unwrap();
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);
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!(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::ToDeviceRequest(r) => Self::try_from(r.clone()),
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())),
}
}

View file

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

View file

@ -19,10 +19,14 @@ mod qrcode;
mod requests;
mod sas;
use std::sync::Arc;
use std::{
collections::{BTreeMap, HashMap},
sync::Arc,
};
use event_enums::OutgoingContent;
pub use machine::VerificationMachine;
use matrix_sdk_common::locks::Mutex;
pub use qrcode::QrVerification;
pub use requests::VerificationRequest;
use ruma::{
@ -35,18 +39,77 @@ use ruma::{
},
AnyMessageEventContent, AnyToDeviceEventContent,
},
DeviceId, EventId, RoomId, UserId,
DeviceId, DeviceIdBox, DeviceKeyId, EventId, RoomId, UserId,
};
pub use sas::{AcceptSettings, Sas};
use tracing::{error, info, trace, warn};
use crate::{
error::SignatureError,
olm::PrivateCrossSigningIdentity,
gossiping::{GossipMachine, GossipRequest},
olm::{PrivateCrossSigningIdentity, ReadOnlyAccount, Session},
store::{Changes, CryptoStore},
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.
#[derive(Clone, Debug)]
pub enum Verification {
@ -307,7 +370,7 @@ pub enum VerificationResult {
#[derive(Clone, Debug)]
pub struct IdentitiesBeingVerified {
private_identity: PrivateCrossSigningIdentity,
store: Arc<dyn CryptoStore>,
store: VerificationStore,
device_being_verified: ReadOnlyDevice,
identity_being_verified: Option<ReadOnlyUserIdentities>,
}
@ -343,7 +406,8 @@ impl IdentitiesBeingVerified {
verified_identities: Option<&[ReadOnlyUserIdentities]>,
) -> Result<VerificationResult, CryptoStoreError> {
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() {
// Something wen't wrong if nothing was verified, we use key
@ -361,7 +425,7 @@ impl IdentitiesBeingVerified {
Err(SignatureError::MissingSigningKey) => {
warn!(
"Can't sign the device keys for {} {}, \
no private user signing key found",
no private device signing key found",
device.user_id(),
device.device_id(),
);
@ -437,6 +501,11 @@ impl IdentitiesBeingVerified {
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.
self.store.save_changes(changes).await?;
@ -445,19 +514,24 @@ impl IdentitiesBeingVerified {
.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(
&self,
verified_identities: Option<&[ReadOnlyUserIdentities]>,
) -> Result<Option<ReadOnlyUserIdentities>, CryptoStoreError> {
) -> Result<(Option<ReadOnlyUserIdentities>, bool), CryptoStoreError> {
// If there wasn't an identity available during the verification flow
// return early as there's nothing to do.
if self.identity_being_verified.is_none() {
return Ok(None);
return Ok((None, false));
}
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
.identity_being_verified
.as_ref()
@ -469,11 +543,14 @@ impl IdentitiesBeingVerified {
"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();
}
true
} else {
false
};
Ok(Some(identity))
(Some(identity), should_request_secrets)
} else {
info!(
user_id = self.other_user_id().as_str(),
@ -482,7 +559,7 @@ impl IdentitiesBeingVerified {
the interactive verification",
);
Ok(None)
(None, false)
}
} else {
warn!(
@ -491,7 +568,7 @@ impl IdentitiesBeingVerified {
verification was going on, not marking the identity as verified.",
);
Ok(None)
(None, false)
}
} else {
info!(
@ -499,8 +576,8 @@ impl IdentitiesBeingVerified {
"The identity of the user was deleted while an interactive \
verification was going on.",
);
Ok(None)
}
(None, false)
})
}
async fn mark_device_as_verified(

View file

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

View file

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

View file

@ -34,12 +34,13 @@ use crate::{
identities::{ReadOnlyDevice, ReadOnlyUserIdentities},
utilities::encode,
verification::event_enums::{MacContent, StartContent},
ReadOnlyAccount,
ReadOnlyAccount, ReadOnlyOwnUserIdentity,
};
#[derive(Clone, Debug)]
pub struct SasIds {
pub account: ReadOnlyAccount,
pub own_identity: Option<ReadOnlyOwnUserIdentity>,
pub other_device: ReadOnlyDevice,
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"),
);
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.
let mut keys = mac.keys().cloned().collect::<Vec<String>>();

View file

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

View file

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

View file

@ -60,7 +60,7 @@ use crate::{
},
Cancelled, Done, FlowId,
},
ReadOnlyAccount,
ReadOnlyAccount, ReadOnlyOwnUserIdentity,
};
const KEY_AGREEMENT_PROTOCOLS: &[KeyAgreementProtocol] =
@ -362,13 +362,21 @@ impl SasState<Created> {
pub fn new(
account: ReadOnlyAccount,
other_device: ReadOnlyDevice,
own_identity: Option<ReadOnlyOwnUserIdentity>,
other_identity: Option<ReadOnlyUserIdentities>,
transaction_id: Option<String>,
) -> SasState<Created> {
let started_from_request = transaction_id.is_some();
let flow_id =
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.
@ -388,22 +396,24 @@ impl SasState<Created> {
event_id: EventId,
account: ReadOnlyAccount,
other_device: ReadOnlyDevice,
own_identity: Option<ReadOnlyOwnUserIdentity>,
other_identity: Option<ReadOnlyUserIdentities>,
) -> SasState<Created> {
let flow_id = FlowId::InRoom(room_id, event_id);
Self::new_helper(flow_id, account, other_device, other_identity, false)
Self::new_helper(flow_id, account, other_device, own_identity, other_identity, false)
}
fn new_helper(
flow_id: FlowId,
account: ReadOnlyAccount,
other_device: ReadOnlyDevice,
own_identity: Option<ReadOnlyOwnUserIdentity>,
other_identity: Option<ReadOnlyUserIdentities>,
started_from_request: bool,
) -> SasState<Created> {
SasState {
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(),
creation_time: Arc::new(Instant::now()),
@ -497,6 +507,7 @@ impl SasState<Started> {
pub fn from_start_event(
account: ReadOnlyAccount,
other_device: ReadOnlyDevice,
own_identity: Option<ReadOnlyOwnUserIdentity>,
other_identity: Option<ReadOnlyUserIdentities>,
flow_id: FlowId,
content: &StartContent,
@ -514,6 +525,7 @@ impl SasState<Started> {
ids: SasIds {
account: account.clone(),
other_device: other_device.clone(),
own_identity: own_identity.clone(),
other_identity: other_identity.clone(),
},
@ -536,7 +548,7 @@ impl SasState<Started> {
Ok(SasState {
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()),
last_event_time: Arc::new(Instant::now()),
@ -1158,7 +1170,7 @@ mod test {
let bob = ReadOnlyAccount::new(&bob_id(), &bob_device_id());
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 flow_id = start_content.flow_id();
@ -1167,6 +1179,7 @@ mod test {
bob.clone(),
alice_device,
None,
None,
flow_id,
&start_content.as_start_content(),
false,
@ -1339,7 +1352,7 @@ mod test {
let bob = ReadOnlyAccount::new(&bob_id(), &bob_device_id());
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 method = start_content.method_mut();
@ -1358,6 +1371,7 @@ mod test {
bob.clone(),
alice_device.clone(),
None,
None,
flow_id,
&content,
false,
@ -1379,6 +1393,7 @@ mod test {
bob.clone(),
alice_device,
None,
None,
flow_id.into(),
&content,
false,