Merge branch 'secret-sharing'
This commit is contained in:
commit
623408913c
32 changed files with 2412 additions and 865 deletions
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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),
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
File diff suppressed because it is too large
Load diff
303
matrix_sdk_crypto/src/gossiping/mod.rs
Normal file
303
matrix_sdk_crypto/src/gossiping/mod.rs
Normal 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()
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
@ -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(¤t_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 \
|
||||
user_id = user_id.as_str(),
|
||||
"User identity for our own user didn't \
|
||||
contain a user signing pubkey",
|
||||
user_id
|
||||
);
|
||||
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.
|
||||
|
|
|
@ -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()),
|
||||
};
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
|
|
|
@ -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)]
|
||||
|
|
|
@ -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.
|
||||
///
|
||||
|
|
|
@ -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,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
for (user_id, info) in r.into_iter() {
|
||||
self.shared_with_set.entry(user_id).or_insert_with(DashMap::new).extend(info)
|
||||
}
|
||||
}),
|
||||
)
|
||||
});
|
||||
|
||||
user_pairs.for_each(|(u, d)| {
|
||||
self.shared_with_set.entry(u).or_insert_with(DashMap::new).extend(d);
|
||||
});
|
||||
|
||||
if self.to_share_with_set.is_empty() {
|
||||
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)
|
||||
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)
|
||||
{
|
||||
Some(ShareState::Shared(message_index))
|
||||
ShareState::Shared(info.message_index)
|
||||
} else {
|
||||
None
|
||||
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)]
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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"))
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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(
|
||||
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();
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -19,21 +19,26 @@ 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 {
|
||||
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.
|
||||
#[derive(Debug, Clone)]
|
||||
|
@ -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)
|
||||
|
|
|
@ -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();
|
||||
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,
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn get_identity(&self, user_id: &UserId) -> Result<Option<UserIdentities>> {
|
||||
Ok(self.inner.get_user_identity(user_id).await?.map(|i| match i {
|
||||
// let own_identity =
|
||||
// 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) => {
|
||||
UserIdentity { inner: i, verification_machine: self.verification_machine.clone() }
|
||||
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<()>;
|
||||
}
|
||||
|
|
|
@ -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,18 +759,21 @@ 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)
|
||||
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<OutgoingKeyRequest> = outgoing_key_requests
|
||||
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
|
||||
let unsent_request: Option<GossipRequest> = unsent_key_requests
|
||||
.remove(request_id.encode())?
|
||||
.map(|r| serde_json::from_slice(&r))
|
||||
.transpose()
|
||||
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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())),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
);
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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) {
|
||||
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,
|
||||
|
|
|
@ -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>>();
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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,
|
||||
|
|
Loading…
Reference in a new issue