use std::sync::Arc;
use as_variant::as_variant;
use eyeball::{ObservableWriteGuard, SharedObservable};
use futures_core::Stream;
use futures_util::StreamExt;
use matrix_sdk_qrcode::{
qrcode::QrCode, EncodingError, QrVerificationData, SelfVerificationData,
SelfVerificationNoMasterKey, VerificationData,
};
use rand::{thread_rng, RngCore};
use ruma::{
api::client::keys::upload_signatures::v3::Request as SignatureUploadRequest,
events::{
key::verification::{
cancel::CancelCode,
done::{KeyVerificationDoneEventContent, ToDeviceKeyVerificationDoneEventContent},
start::{
KeyVerificationStartEventContent, ReciprocateV1Content, StartMethod,
ToDeviceKeyVerificationStartEventContent,
},
},
relation::Reference,
AnyMessageLikeEventContent, AnyToDeviceEventContent,
},
serde::Base64,
DeviceId, OwnedDeviceId, OwnedUserId, RoomId, TransactionId, UserId,
};
use thiserror::Error;
use tracing::{debug, trace};
use vodozemac::Ed25519PublicKey;
use super::{
event_enums::{CancelContent, DoneContent, OutgoingContent, OwnedStartContent, StartContent},
requests::RequestHandle,
CancelInfo, Cancelled, Done, FlowId, IdentitiesBeingVerified, VerificationResult,
VerificationStore,
};
use crate::{
types::requests::{OutgoingVerificationRequest, RoomMessageRequest, ToDeviceRequest},
CryptoStoreError, DeviceData, UserIdentityData,
};
const SECRET_SIZE: usize = 16;
#[derive(Debug, Error)]
pub enum ScanError {
#[error(transparent)]
Store(#[from] CryptoStoreError),
#[error("The keys that are being verified didn't match (expected {expected}, found {found})")]
KeyMismatch {
expected: String,
found: String,
},
#[error("The user {0} is missing a valid cross signing identity")]
MissingCrossSigningIdentity(OwnedUserId),
#[error("The user's {0} device {1} is not E2E capable")]
MissingDeviceKeys(OwnedUserId, OwnedDeviceId),
#[error("The unique verification flow id did not match (expected {expected}, found {found})")]
FlowIdMismatch {
expected: String,
found: String,
},
}
#[derive(Debug, Clone)]
pub enum QrVerificationState {
Started,
Scanned,
Confirmed,
Reciprocated,
Done {
verified_devices: Vec<DeviceData>,
verified_identities: Vec<UserIdentityData>,
},
Cancelled(CancelInfo),
}
impl From<&InnerState> for QrVerificationState {
fn from(value: &InnerState) -> Self {
match value {
InnerState::Created(_) => Self::Started,
InnerState::Scanned(_) => Self::Scanned,
InnerState::Confirmed(_) => Self::Confirmed,
InnerState::Reciprocated(_) => Self::Reciprocated,
InnerState::Done(s) => Self::Done {
verified_devices: s.state.verified_devices.to_vec(),
verified_identities: s.state.verified_master_keys.to_vec(),
},
InnerState::Cancelled(s) => Self::Cancelled(s.state.to_owned().into()),
}
}
}
#[derive(Clone)]
pub struct QrVerification {
flow_id: FlowId,
inner: Arc<QrVerificationData>,
state: SharedObservable<InnerState>,
identities: IdentitiesBeingVerified,
request_handle: Option<RequestHandle>,
we_started: bool,
}
#[cfg(not(tarpaulin_include))]
impl std::fmt::Debug for QrVerification {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("QrVerification")
.field("flow_id", &self.flow_id)
.field("inner", &self.inner)
.field("state", &self.state)
.finish()
}
}
impl QrVerification {
pub fn has_been_scanned(&self) -> bool {
matches!(*self.state.read(), InnerState::Scanned(_))
}
pub fn has_been_confirmed(&self) -> bool {
matches!(*self.state.read(), InnerState::Confirmed(_))
}
pub fn user_id(&self) -> &UserId {
self.identities.user_id()
}
pub fn other_user_id(&self) -> &UserId {
self.identities.other_user_id()
}
pub fn other_device_id(&self) -> &DeviceId {
self.identities.other_device_id()
}
pub fn other_device(&self) -> &DeviceData {
self.identities.other_device()
}
pub fn we_started(&self) -> bool {
self.we_started
}
pub fn cancel_info(&self) -> Option<CancelInfo> {
as_variant!(&*self.state.read(), InnerState::Cancelled(c) => {
c.state.clone().into()
})
}
pub fn is_done(&self) -> bool {
matches!(*self.state.read(), InnerState::Done(_))
}
pub fn is_cancelled(&self) -> bool {
matches!(*self.state.read(), InnerState::Cancelled(_))
}
pub fn is_self_verification(&self) -> bool {
self.identities.is_self_verification()
}
pub fn reciprocated(&self) -> bool {
matches!(*self.state.read(), InnerState::Reciprocated(_))
}
pub fn flow_id(&self) -> &FlowId {
&self.flow_id
}
pub fn room_id(&self) -> Option<&RoomId> {
match self.flow_id() {
FlowId::ToDevice(_) => None,
FlowId::InRoom(r, _) => Some(r),
}
}
pub fn to_qr_code(&self) -> Result<QrCode, EncodingError> {
self.inner.to_qr_code()
}
pub fn to_bytes(&self) -> Result<Vec<u8>, EncodingError> {
self.inner.to_bytes()
}
pub fn cancel(&self) -> Option<OutgoingVerificationRequest> {
self.cancel_with_code(CancelCode::User)
}
pub fn cancel_with_code(&self, code: CancelCode) -> Option<OutgoingVerificationRequest> {
let mut state = self.state.write();
if let Some(request) = &self.request_handle {
request.cancel_with_code(&code);
}
let new_state = QrState::<Cancelled>::new(true, code);
let content = new_state.as_content(self.flow_id());
match &*state {
InnerState::Confirmed(_)
| InnerState::Created(_)
| InnerState::Scanned(_)
| InnerState::Reciprocated(_)
| InnerState::Done(_) => {
ObservableWriteGuard::set(&mut state, InnerState::Cancelled(new_state));
Some(self.content_to_request(content))
}
InnerState::Cancelled(_) => None,
}
}
pub fn reciprocate(&self) -> Option<OutgoingVerificationRequest> {
match &*self.state.read() {
InnerState::Reciprocated(s) => {
Some(self.content_to_request(s.as_content(self.flow_id())))
}
InnerState::Created(_)
| InnerState::Scanned(_)
| InnerState::Confirmed(_)
| InnerState::Done(_)
| InnerState::Cancelled(_) => None,
}
}
pub fn confirm_scanning(&self) -> Option<OutgoingVerificationRequest> {
debug!("User confirmed other side scanned our QR code");
let mut state = self.state.write();
match &*state {
InnerState::Scanned(s) => {
let new_state = s.clone().confirm_scanning();
let content = new_state.as_content(&self.flow_id);
ObservableWriteGuard::set(&mut state, InnerState::Confirmed(new_state));
Some(self.content_to_request(content))
}
InnerState::Created(_)
| InnerState::Cancelled(_)
| InnerState::Confirmed(_)
| InnerState::Reciprocated(_)
| InnerState::Done(_) => None,
}
}
fn content_to_request(&self, content: OutgoingContent) -> OutgoingVerificationRequest {
match content {
OutgoingContent::Room(room_id, content) => {
RoomMessageRequest { room_id, txn_id: TransactionId::new(), content }.into()
}
OutgoingContent::ToDevice(c) => ToDeviceRequest::with_id(
self.identities.other_user_id(),
self.identities.other_device_id().to_owned(),
&c,
TransactionId::new(),
)
.into(),
}
}
async fn mark_as_done(
&self,
new_state: QrState<Done>,
) -> Result<
(Option<OutgoingVerificationRequest>, Option<SignatureUploadRequest>),
CryptoStoreError,
> {
let (devices, identities) = new_state.verified_identities();
let mut new_state = InnerState::Done(new_state);
let (content, request) =
match self.identities.mark_as_done(Some(&devices), Some(&identities)).await? {
VerificationResult::Ok => (None, None),
VerificationResult::Cancel(c) => {
let canceled = QrState::<Cancelled>::new(false, c);
let content = canceled.as_content(self.flow_id());
new_state = InnerState::Cancelled(canceled);
(Some(content), None)
}
VerificationResult::SignatureUpload(s) => (None, Some(s)),
};
self.state.set(new_state);
Ok((content.map(|c| self.content_to_request(c)), request))
}
pub(crate) async fn receive_done(
&self,
content: &DoneContent<'_>,
) -> Result<
(Option<OutgoingVerificationRequest>, Option<SignatureUploadRequest>),
CryptoStoreError,
> {
let state = self.state.get();
Ok(match state {
InnerState::Confirmed(s) => {
let (verified_device, verified_identity) = match &*self.inner {
QrVerificationData::Verification(_) => {
(None, self.identities.identity_being_verified.as_ref())
}
QrVerificationData::SelfVerification(_) => {
(Some(&self.identities.device_being_verified), None)
}
QrVerificationData::SelfVerificationNoMasterKey(_) => {
(None, self.identities.identity_being_verified.as_ref())
}
};
let new_state = s.clone().into_done(content, verified_device, verified_identity);
self.mark_as_done(new_state).await?
}
InnerState::Reciprocated(s) => {
let (verified_device, verified_identity) = match &*self.inner {
QrVerificationData::Verification(_) => {
(None, self.identities.identity_being_verified.as_ref())
}
QrVerificationData::SelfVerification(_) => {
(None, self.identities.identity_being_verified.as_ref())
}
QrVerificationData::SelfVerificationNoMasterKey(_) => {
(Some(&self.identities.device_being_verified), None)
}
};
let new_state = s.clone().into_done(content, verified_device, verified_identity);
let content = Some(new_state.as_content(self.flow_id()));
let (cancel_content, request) = self.mark_as_done(new_state).await?;
if cancel_content.is_some() {
(cancel_content, request)
} else {
(content.map(|c| self.content_to_request(c)), request)
}
}
InnerState::Created(_)
| InnerState::Scanned(_)
| InnerState::Done(_)
| InnerState::Cancelled(_) => (None, None),
})
}
pub(crate) fn receive_reciprocation(
&self,
content: &StartContent<'_>,
) -> Option<OutgoingVerificationRequest> {
let mut state = self.state.write();
match &*state {
InnerState::Created(s) => match s.clone().receive_reciprocate(content) {
Ok(s) => {
ObservableWriteGuard::set(&mut state, InnerState::Scanned(s));
None
}
Err(s) => {
let content = s.as_content(self.flow_id());
ObservableWriteGuard::set(&mut state, InnerState::Cancelled(s));
Some(self.content_to_request(content))
}
},
InnerState::Confirmed(_)
| InnerState::Scanned(_)
| InnerState::Reciprocated(_)
| InnerState::Done(_)
| InnerState::Cancelled(_) => None,
}
}
pub(crate) fn receive_cancel(&self, sender: &UserId, content: &CancelContent<'_>) {
if sender == self.other_user_id() {
let mut state = self.state.write();
let new_state = match &*state {
InnerState::Created(s) => s.clone().into_cancelled(content),
InnerState::Scanned(s) => s.clone().into_cancelled(content),
InnerState::Confirmed(s) => s.clone().into_cancelled(content),
InnerState::Reciprocated(s) => s.clone().into_cancelled(content),
InnerState::Done(_) | InnerState::Cancelled(_) => return,
};
trace!(
?sender,
code = content.cancel_code().as_str(),
"Cancelling a QR verification, other user has cancelled"
);
ObservableWriteGuard::set(&mut state, InnerState::Cancelled(new_state));
}
}
fn generate_secret() -> Base64 {
let mut shared_secret = vec![0u8; SECRET_SIZE];
let mut rng = thread_rng();
rng.fill_bytes(&mut shared_secret);
Base64::new(shared_secret)
}
pub(crate) fn new_self(
flow_id: FlowId,
own_master_key: Ed25519PublicKey,
other_device_key: Ed25519PublicKey,
identities: IdentitiesBeingVerified,
we_started: bool,
request_handle: Option<RequestHandle>,
) -> Self {
let secret = Self::generate_secret();
let inner: QrVerificationData = SelfVerificationData::new(
flow_id.as_str().to_owned(),
own_master_key,
other_device_key,
secret,
)
.into();
Self::new_helper(flow_id, inner, identities, we_started, request_handle)
}
pub(crate) fn new_self_no_master(
store: VerificationStore,
flow_id: FlowId,
own_master_key: Ed25519PublicKey,
identities: IdentitiesBeingVerified,
we_started: bool,
request_handle: Option<RequestHandle>,
) -> QrVerification {
let secret = Self::generate_secret();
let inner: QrVerificationData = SelfVerificationNoMasterKey::new(
flow_id.as_str().to_owned(),
store.account.identity_keys.ed25519,
own_master_key,
secret,
)
.into();
Self::new_helper(flow_id, inner, identities, we_started, request_handle)
}
pub(crate) fn new_cross(
flow_id: FlowId,
own_master_key: Ed25519PublicKey,
other_master_key: Ed25519PublicKey,
identities: IdentitiesBeingVerified,
we_started: bool,
request_handle: Option<RequestHandle>,
) -> Self {
let secret = Self::generate_secret();
let inner: QrVerificationData = VerificationData::new(
flow_id.as_str().to_owned(),
own_master_key,
other_master_key,
secret,
)
.into();
Self::new_helper(flow_id, inner, identities, we_started, request_handle)
}
pub(crate) async fn from_scan(
store: VerificationStore,
other_user_id: OwnedUserId,
other_device_id: OwnedDeviceId,
flow_id: FlowId,
qr_code: QrVerificationData,
we_started: bool,
request_handle: Option<RequestHandle>,
) -> Result<Self, ScanError> {
if flow_id.as_str() != qr_code.flow_id() {
return Err(ScanError::FlowIdMismatch {
expected: flow_id.as_str().to_owned(),
found: qr_code.flow_id().to_owned(),
});
}
let other_device =
store.get_device(&other_user_id, &other_device_id).await?.ok_or_else(|| {
ScanError::MissingDeviceKeys(other_user_id.clone(), other_device_id.clone())
})?;
let identities = store.get_identities(other_device).await?;
let own_identity = identities
.own_identity
.as_ref()
.ok_or_else(|| ScanError::MissingCrossSigningIdentity(store.account.user_id.clone()))?;
let other_identity = identities
.identity_being_verified
.as_ref()
.ok_or_else(|| ScanError::MissingCrossSigningIdentity(other_user_id.clone()))?;
let check_master_key = |key, identity: &UserIdentityData| {
let master_key = identity.master_key().get_first_key().ok_or_else(|| {
ScanError::MissingCrossSigningIdentity(identity.user_id().to_owned())
})?;
if key != master_key {
Err(ScanError::KeyMismatch {
expected: master_key.to_base64(),
found: qr_code.first_key().to_base64(),
})
} else {
Ok(())
}
};
match qr_code {
QrVerificationData::Verification(_) => {
check_master_key(qr_code.first_key(), other_identity)?;
check_master_key(qr_code.second_key(), &own_identity.to_owned().into())?;
}
QrVerificationData::SelfVerification(_) => {
check_master_key(qr_code.first_key(), other_identity)?;
if qr_code.second_key() != store.account.identity_keys.ed25519 {
return Err(ScanError::KeyMismatch {
expected: store.account.identity_keys.ed25519.to_base64(),
found: qr_code.second_key().to_base64(),
});
}
}
QrVerificationData::SelfVerificationNoMasterKey(_) => {
let device_key =
identities.device_being_verified.ed25519_key().ok_or_else(|| {
ScanError::MissingDeviceKeys(other_user_id.clone(), other_device_id.clone())
})?;
if qr_code.first_key() != device_key {
return Err(ScanError::KeyMismatch {
expected: device_key.to_base64(),
found: qr_code.first_key().to_base64(),
});
}
check_master_key(qr_code.second_key(), other_identity)?;
}
};
let secret = qr_code.secret().to_owned();
let own_device_id = store.account.device_id.clone();
Ok(Self {
flow_id,
inner: qr_code.into(),
state: SharedObservable::new(InnerState::Reciprocated(QrState {
state: Reciprocated { secret, own_device_id },
})),
identities,
we_started,
request_handle,
})
}
fn new_helper(
flow_id: FlowId,
inner: QrVerificationData,
identities: IdentitiesBeingVerified,
we_started: bool,
request_handle: Option<RequestHandle>,
) -> Self {
let secret = inner.secret().to_owned();
Self {
flow_id,
inner: inner.into(),
state: SharedObservable::new(InnerState::Created(QrState {
state: Created { secret },
})),
identities,
we_started,
request_handle,
}
}
pub fn changes(&self) -> impl Stream<Item = QrVerificationState> {
self.state.subscribe().map(|s| (&s).into())
}
pub fn state(&self) -> QrVerificationState {
(&*self.state.read()).into()
}
}
#[derive(Debug, Clone)]
enum InnerState {
Created(QrState<Created>),
Scanned(QrState<Scanned>),
Confirmed(QrState<Confirmed>),
Reciprocated(QrState<Reciprocated>),
Done(QrState<Done>),
Cancelled(QrState<Cancelled>),
}
#[derive(Clone, Debug)]
struct QrState<S: Clone> {
state: S,
}
impl<S: Clone> QrState<S> {
pub fn into_cancelled(self, content: &CancelContent<'_>) -> QrState<Cancelled> {
QrState { state: Cancelled::new(false, content.cancel_code().to_owned()) }
}
}
#[derive(Clone, Debug)]
struct Created {
secret: Base64,
}
#[derive(Clone, Debug)]
struct Scanned {}
#[derive(Clone, Debug)]
struct Confirmed {}
#[derive(Clone, Debug)]
struct Reciprocated {
own_device_id: OwnedDeviceId,
secret: Base64,
}
impl Reciprocated {
fn as_content(&self, flow_id: &FlowId) -> OutgoingContent {
let content = ReciprocateV1Content::new(self.secret.clone());
let method = StartMethod::ReciprocateV1(content);
let content: OwnedStartContent = match flow_id {
FlowId::ToDevice(t) => ToDeviceKeyVerificationStartEventContent::new(
self.own_device_id.clone(),
t.clone(),
method,
)
.into(),
FlowId::InRoom(r, e) => (
r.clone(),
KeyVerificationStartEventContent::new(
self.own_device_id.clone(),
method,
Reference::new(e.clone()),
),
)
.into(),
};
content.into()
}
}
impl QrState<Scanned> {
fn confirm_scanning(self) -> QrState<Confirmed> {
QrState { state: Confirmed {} }
}
}
impl QrState<Cancelled> {
fn new(cancelled_by_us: bool, cancel_code: CancelCode) -> Self {
QrState { state: Cancelled::new(cancelled_by_us, cancel_code) }
}
fn as_content(&self, flow_id: &FlowId) -> OutgoingContent {
self.state.as_content(flow_id)
}
}
impl QrState<Created> {
fn receive_reciprocate(
self,
content: &StartContent<'_>,
) -> Result<QrState<Scanned>, QrState<Cancelled>> {
match content.method() {
StartMethod::ReciprocateV1(m) => {
if self.state.secret == m.secret {
Ok(QrState { state: Scanned {} })
} else {
Err(QrState::<Cancelled>::new(false, CancelCode::KeyMismatch))
}
}
_ => Err(QrState::<Cancelled>::new(false, CancelCode::UnknownMethod)),
}
}
}
impl QrState<Done> {
fn as_content(&self, flow_id: &FlowId) -> OutgoingContent {
self.state.as_content(flow_id)
}
fn verified_identities(&self) -> (Arc<[DeviceData]>, Arc<[UserIdentityData]>) {
(self.state.verified_devices.clone(), self.state.verified_master_keys.clone())
}
}
impl QrState<Confirmed> {
fn into_done(
self,
_: &DoneContent<'_>,
verified_device: Option<&DeviceData>,
verified_identity: Option<&UserIdentityData>,
) -> QrState<Done> {
let devices: Vec<_> = verified_device.into_iter().cloned().collect();
let identities: Vec<_> = verified_identity.into_iter().cloned().collect();
QrState {
state: Done {
verified_devices: devices.into(),
verified_master_keys: identities.into(),
},
}
}
fn as_content(&self, flow_id: &FlowId) -> OutgoingContent {
match flow_id {
FlowId::ToDevice(t) => AnyToDeviceEventContent::KeyVerificationDone(
ToDeviceKeyVerificationDoneEventContent::new(t.to_owned()),
)
.into(),
FlowId::InRoom(r, e) => (
r.to_owned(),
AnyMessageLikeEventContent::KeyVerificationDone(
KeyVerificationDoneEventContent::new(Reference::new(e.to_owned())),
),
)
.into(),
}
}
}
impl QrState<Reciprocated> {
fn as_content(&self, flow_id: &FlowId) -> OutgoingContent {
self.state.as_content(flow_id)
}
fn into_done(
self,
_: &DoneContent<'_>,
verified_device: Option<&DeviceData>,
verified_identity: Option<&UserIdentityData>,
) -> QrState<Done> {
let devices: Vec<_> = verified_device.into_iter().cloned().collect();
let identities: Vec<_> = verified_identity.into_iter().cloned().collect();
QrState {
state: Done {
verified_devices: devices.into(),
verified_master_keys: identities.into(),
},
}
}
}
#[cfg(test)]
mod tests {
use std::sync::Arc;
use assert_matches::assert_matches;
use matrix_sdk_qrcode::QrVerificationData;
use matrix_sdk_test::async_test;
use ruma::{device_id, event_id, room_id, user_id, DeviceId, UserId};
use tokio::sync::Mutex;
use crate::{
olm::{Account, PrivateCrossSigningIdentity},
store::{Changes, CryptoStoreWrapper, MemoryStore},
verification::{
event_enums::{DoneContent, OutgoingContent, StartContent},
FlowId, VerificationStore,
},
DeviceData, QrVerification, QrVerificationState,
};
fn user_id() -> &'static UserId {
user_id!("@example:localhost")
}
fn memory_store(user_id: &UserId, device_id: &DeviceId) -> Arc<CryptoStoreWrapper> {
Arc::new(CryptoStoreWrapper::new(user_id, device_id, MemoryStore::new()))
}
fn device_id() -> &'static DeviceId {
device_id!("DEVICEID")
}
#[async_test]
async fn test_verification_creation() {
let account = Account::with_device_id(user_id(), device_id());
let store = memory_store(account.user_id(), account.device_id());
let private_identity = PrivateCrossSigningIdentity::new(user_id().to_owned());
let master_key = private_identity.master_public_key().await.unwrap();
let master_key = master_key.get_first_key().unwrap().to_owned();
let store = VerificationStore {
account: account.static_data.clone(),
inner: store,
private_identity: Mutex::new(private_identity).into(),
};
let flow_id = FlowId::ToDevice("test_transaction".into());
let device_key = account.static_data.identity_keys.ed25519;
let alice_device = DeviceData::from_account(&account);
let identities = store.get_identities(alice_device).await.unwrap();
let verification = QrVerification::new_self_no_master(
store.clone(),
flow_id.clone(),
master_key,
identities.clone(),
false,
None,
);
assert_matches!(verification.state(), QrVerificationState::Started);
assert_eq!(verification.inner.first_key(), device_key);
assert_eq!(verification.inner.second_key(), master_key);
let verification = QrVerification::new_self(
flow_id,
master_key,
device_key,
identities.clone(),
false,
None,
);
assert_matches!(verification.state(), QrVerificationState::Started);
assert_eq!(verification.inner.first_key(), master_key);
assert_eq!(verification.inner.second_key(), device_key);
let bob_identity = PrivateCrossSigningIdentity::new(user_id!("@bob:example").to_owned());
let bob_master_key = bob_identity.master_public_key().await.unwrap();
let bob_master_key = bob_master_key.get_first_key().unwrap().to_owned();
let flow_id =
FlowId::InRoom(room_id!("!test:example").to_owned(), event_id!("$EVENTID").to_owned());
let verification =
QrVerification::new_cross(flow_id, master_key, bob_master_key, identities, false, None);
assert_matches!(verification.state(), QrVerificationState::Started);
assert_eq!(verification.inner.first_key(), master_key);
assert_eq!(verification.inner.second_key(), bob_master_key);
}
#[async_test]
async fn test_reciprocate_receival() {
let test = |flow_id: FlowId| async move {
let alice_account = Account::with_device_id(user_id(), device_id());
let store = memory_store(alice_account.user_id(), alice_account.device_id());
let private_identity = PrivateCrossSigningIdentity::new(user_id().to_owned());
let store = VerificationStore {
account: alice_account.static_data.clone(),
inner: store,
private_identity: Mutex::new(private_identity).into(),
};
let bob_account =
Account::with_device_id(alice_account.user_id(), device_id!("BOBDEVICE"));
let private_identity = PrivateCrossSigningIdentity::new(user_id().to_owned());
let identity = private_identity.to_public_identity().await.unwrap();
let master_key = private_identity.master_public_key().await.unwrap();
let master_key = master_key.get_first_key().unwrap().to_owned();
let alice_device = DeviceData::from_account(&alice_account);
let bob_device = DeviceData::from_account(&bob_account);
let mut changes = Changes::default();
changes.identities.new.push(identity.clone().into());
changes.devices.new.push(bob_device.clone());
store.save_changes(changes).await.unwrap();
let identities = store.get_identities(alice_device.clone()).await.unwrap();
let alice_verification = QrVerification::new_self_no_master(
store,
flow_id.clone(),
master_key,
identities,
false,
None,
);
assert_matches!(alice_verification.state(), QrVerificationState::Started);
let bob_store = memory_store(bob_account.user_id(), bob_account.device_id());
let private_identity = PrivateCrossSigningIdentity::new(user_id().to_owned());
let bob_store = VerificationStore {
account: bob_account.static_data.clone(),
inner: bob_store,
private_identity: Mutex::new(private_identity).into(),
};
let mut changes = Changes::default();
changes.identities.new.push(identity.into());
changes.devices.new.push(alice_device.clone());
bob_store.save_changes(changes).await.unwrap();
let qr_code = alice_verification.to_bytes().unwrap();
let qr_code = QrVerificationData::from_bytes(qr_code).unwrap();
let bob_verification = QrVerification::from_scan(
bob_store,
alice_account.user_id().to_owned(),
alice_account.device_id().to_owned(),
flow_id,
qr_code,
false,
None,
)
.await
.unwrap();
let request = bob_verification.reciprocate().unwrap();
assert_matches!(bob_verification.state(), QrVerificationState::Reciprocated);
let content = OutgoingContent::try_from(request).unwrap();
let content = StartContent::try_from(&content).unwrap();
alice_verification.receive_reciprocation(&content);
assert_matches!(alice_verification.state(), QrVerificationState::Scanned);
let request = alice_verification.confirm_scanning().unwrap();
assert_matches!(alice_verification.state(), QrVerificationState::Confirmed);
let content = OutgoingContent::try_from(request).unwrap();
let content = DoneContent::try_from(&content).unwrap();
assert!(!alice_verification.is_done());
assert!(!bob_verification.is_done());
let (request, _) = bob_verification.receive_done(&content).await.unwrap();
let content = OutgoingContent::try_from(request.unwrap()).unwrap();
let content = DoneContent::try_from(&content).unwrap();
alice_verification.receive_done(&content).await.unwrap();
assert_matches!(alice_verification.state(), QrVerificationState::Done { .. });
assert_matches!(bob_verification.state(), QrVerificationState::Done { .. });
assert!(alice_verification.is_done());
assert!(bob_verification.is_done());
let identity = alice_verification
.identities
.store
.get_user_identity(alice_account.user_id())
.await
.unwrap()
.unwrap();
let identity = identity.own().unwrap();
assert!(!bob_device.is_locally_trusted());
assert!(alice_device.is_locally_trusted());
assert!(identity.is_verified());
};
let flow_id = FlowId::ToDevice("test_transaction".into());
test(flow_id).await;
let flow_id =
FlowId::InRoom(room_id!("!test:example").to_owned(), event_id!("$EVENTID").to_owned());
test(flow_id).await;
}
}