722 lines
23 KiB
Rust
722 lines
23 KiB
Rust
// 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 cache;
|
|
mod event_enums;
|
|
mod machine;
|
|
#[cfg(feature = "qrcode")]
|
|
mod qrcode;
|
|
mod requests;
|
|
mod sas;
|
|
|
|
use std::{
|
|
collections::{BTreeMap, HashMap},
|
|
sync::Arc,
|
|
};
|
|
|
|
use event_enums::OutgoingContent;
|
|
pub use machine::VerificationMachine;
|
|
use matrix_sdk_common::locks::Mutex;
|
|
#[cfg(feature = "qrcode")]
|
|
#[cfg_attr(feature = "docs", doc(cfg(qrcode)))]
|
|
pub use qrcode::QrVerification;
|
|
pub use requests::VerificationRequest;
|
|
use ruma::{
|
|
api::client::r0::keys::upload_signatures::Request as SignatureUploadRequest,
|
|
events::{
|
|
key::verification::{
|
|
cancel::{CancelCode, CancelEventContent, CancelToDeviceEventContent},
|
|
done::{DoneEventContent, DoneToDeviceEventContent},
|
|
Relation,
|
|
},
|
|
AnyMessageEventContent, AnyToDeviceEventContent,
|
|
},
|
|
DeviceId, DeviceIdBox, DeviceKeyId, EventId, RoomId, UserId,
|
|
};
|
|
pub use sas::{AcceptSettings, Sas};
|
|
use tracing::{error, info, trace, warn};
|
|
|
|
use crate::{
|
|
error::SignatureError,
|
|
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 {
|
|
/// The `m.sas.v1` verification variant.
|
|
SasV1(Sas),
|
|
#[cfg(feature = "qrcode")]
|
|
#[cfg_attr(feature = "docs", doc(cfg(qrcode)))]
|
|
/// The `m.qr_code.*.v1` verification variant.
|
|
QrV1(QrVerification),
|
|
}
|
|
|
|
impl Verification {
|
|
/// Try to deconstruct this verification enum into a SAS verification.
|
|
pub fn sas_v1(self) -> Option<Sas> {
|
|
#[allow(irrefutable_let_patterns)]
|
|
if let Verification::SasV1(sas) = self {
|
|
Some(sas)
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
|
|
#[cfg(feature = "qrcode")]
|
|
#[cfg_attr(feature = "docs", doc(cfg(qrcode)))]
|
|
/// Try to deconstruct this verification enum into a QR code verification.
|
|
pub fn qr_v1(self) -> Option<QrVerification> {
|
|
if let Verification::QrV1(qr) = self {
|
|
Some(qr)
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
|
|
/// Has this verification finished.
|
|
pub fn is_done(&self) -> bool {
|
|
match self {
|
|
Verification::SasV1(s) => s.is_done(),
|
|
#[cfg(feature = "qrcode")]
|
|
Verification::QrV1(qr) => qr.is_done(),
|
|
}
|
|
}
|
|
|
|
/// Get the ID that uniquely identifies this verification flow.
|
|
pub fn flow_id(&self) -> &str {
|
|
match self {
|
|
Verification::SasV1(s) => s.flow_id().as_str(),
|
|
#[cfg(feature = "qrcode")]
|
|
Verification::QrV1(qr) => qr.flow_id().as_str(),
|
|
}
|
|
}
|
|
|
|
/// Has the verification been cancelled.
|
|
pub fn is_cancelled(&self) -> bool {
|
|
match self {
|
|
Verification::SasV1(s) => s.is_cancelled(),
|
|
#[cfg(feature = "qrcode")]
|
|
Verification::QrV1(qr) => qr.is_cancelled(),
|
|
}
|
|
}
|
|
|
|
/// Get our own user id that is participating in this verification.
|
|
pub fn user_id(&self) -> &UserId {
|
|
match self {
|
|
Verification::SasV1(v) => v.user_id(),
|
|
#[cfg(feature = "qrcode")]
|
|
Verification::QrV1(v) => v.user_id(),
|
|
}
|
|
}
|
|
|
|
/// Get the other user id that is participating in this verification.
|
|
pub fn other_user(&self) -> &UserId {
|
|
match self {
|
|
Verification::SasV1(s) => s.other_user_id(),
|
|
#[cfg(feature = "qrcode")]
|
|
Verification::QrV1(qr) => qr.other_user_id(),
|
|
}
|
|
}
|
|
|
|
/// Is this a verification verifying a device that belongs to us.
|
|
pub fn is_self_verification(&self) -> bool {
|
|
match self {
|
|
Verification::SasV1(v) => v.is_self_verification(),
|
|
#[cfg(feature = "qrcode")]
|
|
Verification::QrV1(v) => v.is_self_verification(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<Sas> for Verification {
|
|
fn from(sas: Sas) -> Self {
|
|
Self::SasV1(sas)
|
|
}
|
|
}
|
|
|
|
#[cfg(feature = "qrcode")]
|
|
#[cfg_attr(feature = "docs", doc(cfg(qrcode)))]
|
|
impl From<QrVerification> for Verification {
|
|
fn from(qr: QrVerification) -> Self {
|
|
Self::QrV1(qr)
|
|
}
|
|
}
|
|
|
|
/// The verification state indicating that the verification finished
|
|
/// successfully.
|
|
///
|
|
/// We can now mark the device in our verified devices list as verified and sign
|
|
/// the master keys in the verified devices list.
|
|
#[derive(Clone, Debug)]
|
|
pub struct Done {
|
|
verified_devices: Arc<[ReadOnlyDevice]>,
|
|
verified_master_keys: Arc<[ReadOnlyUserIdentities]>,
|
|
}
|
|
|
|
impl Done {
|
|
pub fn as_content(&self, flow_id: &FlowId) -> OutgoingContent {
|
|
match flow_id {
|
|
FlowId::ToDevice(t) => AnyToDeviceEventContent::KeyVerificationDone(
|
|
DoneToDeviceEventContent::new(t.to_owned()),
|
|
)
|
|
.into(),
|
|
FlowId::InRoom(r, e) => (
|
|
r.to_owned(),
|
|
AnyMessageEventContent::KeyVerificationDone(DoneEventContent::new(Relation::new(
|
|
e.to_owned(),
|
|
))),
|
|
)
|
|
.into(),
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Information about the cancellation of a verification request or verification
|
|
/// flow.
|
|
#[derive(Clone, Debug)]
|
|
pub struct CancelInfo {
|
|
cancelled_by_us: bool,
|
|
cancel_code: CancelCode,
|
|
reason: &'static str,
|
|
}
|
|
|
|
impl CancelInfo {
|
|
/// Get the human readable reason of the cancellation.
|
|
pub fn reason(&self) -> &'static str {
|
|
self.reason
|
|
}
|
|
|
|
/// Get the `CancelCode` that cancelled this verification.
|
|
pub fn cancel_code(&self) -> &CancelCode {
|
|
&self.cancel_code
|
|
}
|
|
|
|
/// Was the verification cancelled by us?
|
|
pub fn cancelled_by_us(&self) -> bool {
|
|
self.cancelled_by_us
|
|
}
|
|
}
|
|
|
|
impl From<Cancelled> for CancelInfo {
|
|
fn from(c: Cancelled) -> Self {
|
|
Self { cancelled_by_us: c.cancelled_by_us, cancel_code: c.cancel_code, reason: c.reason }
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Debug)]
|
|
pub struct Cancelled {
|
|
cancelled_by_us: bool,
|
|
cancel_code: CancelCode,
|
|
reason: &'static str,
|
|
}
|
|
|
|
impl Cancelled {
|
|
fn new(cancelled_by_us: bool, code: CancelCode) -> Self {
|
|
let reason = match code {
|
|
CancelCode::Accepted => {
|
|
"A m.key.verification.request was accepted by a different device."
|
|
}
|
|
CancelCode::InvalidMessage => "The received message was invalid.",
|
|
CancelCode::KeyMismatch => "The expected key did not match the verified one",
|
|
CancelCode::Timeout => "The verification process timed out.",
|
|
CancelCode::UnexpectedMessage => "The device received an unexpected message.",
|
|
CancelCode::UnknownMethod => {
|
|
"The device does not know how to handle the requested method."
|
|
}
|
|
CancelCode::UnknownTransaction => {
|
|
"The device does not know about the given transaction ID."
|
|
}
|
|
CancelCode::User => "The user cancelled the verification.",
|
|
CancelCode::UserMismatch => "The expected user did not match the verified user",
|
|
_ => "Unknown cancel reason",
|
|
};
|
|
|
|
Self { cancelled_by_us, cancel_code: code, reason }
|
|
}
|
|
|
|
pub fn as_content(&self, flow_id: &FlowId) -> OutgoingContent {
|
|
match flow_id {
|
|
FlowId::ToDevice(s) => {
|
|
AnyToDeviceEventContent::KeyVerificationCancel(CancelToDeviceEventContent::new(
|
|
s.clone(),
|
|
self.reason.to_string(),
|
|
self.cancel_code.clone(),
|
|
))
|
|
.into()
|
|
}
|
|
|
|
FlowId::InRoom(r, e) => (
|
|
r.clone(),
|
|
AnyMessageEventContent::KeyVerificationCancel(CancelEventContent::new(
|
|
self.reason.to_string(),
|
|
self.cancel_code.clone(),
|
|
Relation::new(e.clone()),
|
|
)),
|
|
)
|
|
.into(),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Debug, Hash, PartialEq, PartialOrd)]
|
|
pub enum FlowId {
|
|
ToDevice(String),
|
|
InRoom(RoomId, EventId),
|
|
}
|
|
|
|
impl FlowId {
|
|
pub fn room_id(&self) -> Option<&RoomId> {
|
|
if let FlowId::InRoom(r, _) = &self {
|
|
Some(r)
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
|
|
pub fn as_str(&self) -> &str {
|
|
match self {
|
|
FlowId::InRoom(_, r) => r.as_str(),
|
|
FlowId::ToDevice(t) => t.as_str(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<String> for FlowId {
|
|
fn from(transaction_id: String) -> Self {
|
|
FlowId::ToDevice(transaction_id)
|
|
}
|
|
}
|
|
|
|
impl From<(RoomId, EventId)> for FlowId {
|
|
fn from(ids: (RoomId, EventId)) -> Self {
|
|
FlowId::InRoom(ids.0, ids.1)
|
|
}
|
|
}
|
|
|
|
impl From<(&RoomId, &EventId)> for FlowId {
|
|
fn from(ids: (&RoomId, &EventId)) -> Self {
|
|
FlowId::InRoom(ids.0.to_owned(), ids.1.to_owned())
|
|
}
|
|
}
|
|
|
|
/// A result of a verification flow.
|
|
#[derive(Clone, Debug)]
|
|
pub enum VerificationResult {
|
|
/// The verification succeeded, nothing needs to be done.
|
|
Ok,
|
|
/// The verification was canceled.
|
|
Cancel(CancelCode),
|
|
/// The verification is done and has signatures that need to be uploaded.
|
|
SignatureUpload(SignatureUploadRequest),
|
|
}
|
|
|
|
#[derive(Clone, Debug)]
|
|
pub struct IdentitiesBeingVerified {
|
|
private_identity: PrivateCrossSigningIdentity,
|
|
store: VerificationStore,
|
|
device_being_verified: ReadOnlyDevice,
|
|
identity_being_verified: Option<ReadOnlyUserIdentities>,
|
|
}
|
|
|
|
impl IdentitiesBeingVerified {
|
|
#[cfg(feature = "qrcode")]
|
|
async fn can_sign_devices(&self) -> bool {
|
|
self.private_identity.can_sign_devices().await
|
|
}
|
|
|
|
fn user_id(&self) -> &UserId {
|
|
self.private_identity.user_id()
|
|
}
|
|
|
|
fn is_self_verification(&self) -> bool {
|
|
self.user_id() == self.other_user_id()
|
|
}
|
|
|
|
fn other_user_id(&self) -> &UserId {
|
|
self.device_being_verified.user_id()
|
|
}
|
|
|
|
fn other_device_id(&self) -> &DeviceId {
|
|
self.device_being_verified.device_id()
|
|
}
|
|
|
|
fn other_device(&self) -> &ReadOnlyDevice {
|
|
&self.device_being_verified
|
|
}
|
|
|
|
pub async fn mark_as_done(
|
|
&self,
|
|
verified_devices: Option<&[ReadOnlyDevice]>,
|
|
verified_identities: Option<&[ReadOnlyUserIdentities]>,
|
|
) -> Result<VerificationResult, CryptoStoreError> {
|
|
let device = self.mark_device_as_verified(verified_devices).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
|
|
// mismatch here, since it's the closest to nothing was verified
|
|
return Ok(VerificationResult::Cancel(CancelCode::KeyMismatch));
|
|
}
|
|
|
|
let mut changes = Changes::default();
|
|
|
|
let signature_request = if let Some(device) = device {
|
|
// We only sign devices of our own user here.
|
|
let signature_request = if device.user_id() == self.user_id() {
|
|
match self.private_identity.sign_device(&device).await {
|
|
Ok(r) => Some(r),
|
|
Err(SignatureError::MissingSigningKey) => {
|
|
warn!(
|
|
"Can't sign the device keys for {} {}, \
|
|
no private device signing key found",
|
|
device.user_id(),
|
|
device.device_id(),
|
|
);
|
|
|
|
None
|
|
}
|
|
Err(e) => {
|
|
error!(
|
|
"Error signing device keys for {} {} {:?}",
|
|
device.user_id(),
|
|
device.device_id(),
|
|
e
|
|
);
|
|
None
|
|
}
|
|
}
|
|
} else {
|
|
None
|
|
};
|
|
|
|
changes.devices.changed.push(device);
|
|
signature_request
|
|
} else {
|
|
None
|
|
};
|
|
|
|
let identity_signature_request = if let Some(i) = identity {
|
|
// We only sign other users here.
|
|
let request = if let Some(i) = i.other() {
|
|
// Signing can fail if the user signing key is missing.
|
|
match self.private_identity.sign_user(i).await {
|
|
Ok(r) => Some(r),
|
|
Err(SignatureError::MissingSigningKey) => {
|
|
warn!(
|
|
"Can't sign the public cross signing keys for {}, \
|
|
no private user signing key found",
|
|
i.user_id()
|
|
);
|
|
None
|
|
}
|
|
Err(e) => {
|
|
error!(
|
|
"Error signing the public cross signing keys for {} {:?}",
|
|
i.user_id(),
|
|
e
|
|
);
|
|
None
|
|
}
|
|
}
|
|
} else {
|
|
None
|
|
};
|
|
|
|
changes.identities.changed.push(i);
|
|
request
|
|
} else {
|
|
None
|
|
};
|
|
|
|
// If there are two signature upload requests, merge them. Otherwise
|
|
// use the one we have or None.
|
|
//
|
|
// Realistically at most one request will be used but let's make
|
|
// this future proof.
|
|
let merged_request = if let Some(mut r) = signature_request {
|
|
if let Some(user_request) = identity_signature_request {
|
|
r.signed_keys.extend(user_request.signed_keys);
|
|
Some(r)
|
|
} else {
|
|
Some(r)
|
|
}
|
|
} else {
|
|
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?;
|
|
|
|
Ok(merged_request
|
|
.map(VerificationResult::SignatureUpload)
|
|
.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>, 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, false));
|
|
}
|
|
|
|
let identity = self.store.get_user_identity(self.other_user_id()).await?;
|
|
|
|
Ok(if let Some(identity) = identity {
|
|
if self
|
|
.identity_being_verified
|
|
.as_ref()
|
|
.map_or(false, |i| i.master_key() == identity.master_key())
|
|
{
|
|
if verified_identities.map_or(false, |i| i.contains(&identity)) {
|
|
trace!(
|
|
user_id = self.other_user_id().as_str(),
|
|
"Marking the user identity of as verified."
|
|
);
|
|
|
|
let should_request_secrets = if let ReadOnlyUserIdentities::Own(i) = &identity {
|
|
i.mark_as_verified();
|
|
true
|
|
} else {
|
|
false
|
|
};
|
|
|
|
(Some(identity), should_request_secrets)
|
|
} else {
|
|
info!(
|
|
user_id = self.other_user_id().as_str(),
|
|
"The interactive verification process didn't verify \
|
|
the user identity of the user that participated in \
|
|
the interactive verification",
|
|
);
|
|
|
|
(None, false)
|
|
}
|
|
} else {
|
|
warn!(
|
|
user_id = self.other_user_id().as_str(),
|
|
"The master keys of the user have changed while an interactive \
|
|
verification was going on, not marking the identity as verified.",
|
|
);
|
|
|
|
(None, false)
|
|
}
|
|
} else {
|
|
info!(
|
|
user_id = self.other_user_id().as_str(),
|
|
"The identity of the user was deleted while an interactive \
|
|
verification was going on.",
|
|
);
|
|
(None, false)
|
|
})
|
|
}
|
|
|
|
async fn mark_device_as_verified(
|
|
&self,
|
|
verified_devices: Option<&[ReadOnlyDevice]>,
|
|
) -> Result<Option<ReadOnlyDevice>, CryptoStoreError> {
|
|
let device = self.store.get_device(self.other_user_id(), self.other_device_id()).await?;
|
|
|
|
if let Some(device) = device {
|
|
if device.keys() == self.device_being_verified.keys() {
|
|
if verified_devices.map_or(false, |v| v.contains(&device)) {
|
|
trace!(
|
|
user_id = device.user_id().as_str(),
|
|
device_id = device.device_id().as_str(),
|
|
"Marking device as verified.",
|
|
);
|
|
|
|
device.set_trust_state(LocalTrust::Verified);
|
|
|
|
Ok(Some(device))
|
|
} else {
|
|
info!(
|
|
user_id = device.user_id().as_str(),
|
|
device_id = device.device_id().as_str(),
|
|
"The interactive verification process didn't verify \
|
|
the device",
|
|
);
|
|
|
|
Ok(None)
|
|
}
|
|
} else {
|
|
warn!(
|
|
user_id = device.user_id().as_str(),
|
|
device_id = device.device_id().as_str(),
|
|
"The device keys have changed while an interactive \
|
|
verification was going on, not marking the device as verified.",
|
|
);
|
|
Ok(None)
|
|
}
|
|
} else {
|
|
let device = &self.device_being_verified;
|
|
|
|
info!(
|
|
user_id = device.user_id().as_str(),
|
|
device_id = device.device_id().as_str(),
|
|
"The device was deleted while an interactive verification was \
|
|
going on.",
|
|
);
|
|
|
|
Ok(None)
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
pub(crate) mod test {
|
|
use std::convert::TryInto;
|
|
|
|
use ruma::{
|
|
events::{AnyToDeviceEvent, AnyToDeviceEventContent, ToDeviceEvent},
|
|
UserId,
|
|
};
|
|
|
|
use super::event_enums::OutgoingContent;
|
|
use crate::{
|
|
requests::{OutgoingRequest, OutgoingRequests},
|
|
OutgoingVerificationRequest,
|
|
};
|
|
|
|
pub(crate) fn request_to_event(
|
|
sender: &UserId,
|
|
request: &OutgoingVerificationRequest,
|
|
) -> AnyToDeviceEvent {
|
|
let content =
|
|
request.to_owned().try_into().expect("Can't fetch content out of the request");
|
|
wrap_any_to_device_content(sender, content)
|
|
}
|
|
|
|
pub(crate) fn outgoing_request_to_event(
|
|
sender: &UserId,
|
|
request: &OutgoingRequest,
|
|
) -> AnyToDeviceEvent {
|
|
match request.request() {
|
|
OutgoingRequests::ToDeviceRequest(r) => request_to_event(sender, &r.clone().into()),
|
|
_ => panic!("Unsupported outgoing request"),
|
|
}
|
|
}
|
|
|
|
pub(crate) fn wrap_any_to_device_content(
|
|
sender: &UserId,
|
|
content: OutgoingContent,
|
|
) -> AnyToDeviceEvent {
|
|
let content = if let OutgoingContent::ToDevice(c) = content { c } else { unreachable!() };
|
|
|
|
match content {
|
|
AnyToDeviceEventContent::KeyVerificationKey(c) => {
|
|
AnyToDeviceEvent::KeyVerificationKey(ToDeviceEvent {
|
|
sender: sender.clone(),
|
|
content: c,
|
|
})
|
|
}
|
|
AnyToDeviceEventContent::KeyVerificationStart(c) => {
|
|
AnyToDeviceEvent::KeyVerificationStart(ToDeviceEvent {
|
|
sender: sender.clone(),
|
|
content: c,
|
|
})
|
|
}
|
|
AnyToDeviceEventContent::KeyVerificationAccept(c) => {
|
|
AnyToDeviceEvent::KeyVerificationAccept(ToDeviceEvent {
|
|
sender: sender.clone(),
|
|
content: c,
|
|
})
|
|
}
|
|
AnyToDeviceEventContent::KeyVerificationMac(c) => {
|
|
AnyToDeviceEvent::KeyVerificationMac(ToDeviceEvent {
|
|
sender: sender.clone(),
|
|
content: c,
|
|
})
|
|
}
|
|
|
|
_ => unreachable!(),
|
|
}
|
|
}
|
|
}
|