crypto: Add initial support for QR code verification
parent
71aba433da
commit
00c3921d2a
|
@ -20,6 +20,7 @@ sled_cryptostore = ["sled"]
|
|||
docs = ["sled_cryptostore"]
|
||||
|
||||
[dependencies]
|
||||
matrix-qrcode = { version = "0.1.0", path = "../matrix_qrcode" }
|
||||
matrix-sdk-common = { version = "0.2.0", path = "../matrix_sdk_common" }
|
||||
ruma = { version = "0.1.2", features = ["client-api-c", "unstable-pre-spec"] }
|
||||
|
||||
|
|
|
@ -213,6 +213,14 @@ impl MasterPubkey {
|
|||
self.0.keys.get(key_id.as_str()).map(|k| k.as_str())
|
||||
}
|
||||
|
||||
/// Get the first available master key.
|
||||
///
|
||||
/// There's usually only a single master key so this will usually fetch the
|
||||
/// only key.
|
||||
pub fn get_first_key(&self) -> Option<&str> {
|
||||
self.0.keys.values().map(|k| k.as_str()).next()
|
||||
}
|
||||
|
||||
/// Check if the given cross signing sub-key is signed by the master key.
|
||||
///
|
||||
/// # Arguments
|
||||
|
@ -803,7 +811,7 @@ pub(crate) mod test {
|
|||
Arc::new(MemoryStore::new()),
|
||||
);
|
||||
|
||||
let public_identity = identity.as_public_identity().await.unwrap();
|
||||
let public_identity = identity.to_public_identity().await.unwrap();
|
||||
|
||||
let mut device = Device {
|
||||
inner: device,
|
||||
|
|
|
@ -48,6 +48,7 @@ pub use identities::{
|
|||
Device, LocalTrust, OwnUserIdentity, ReadOnlyDevice, UserDevices, UserIdentities, UserIdentity,
|
||||
};
|
||||
pub use machine::OlmMachine;
|
||||
pub use matrix_qrcode;
|
||||
pub use olm::EncryptionSettings;
|
||||
pub(crate) use olm::ReadOnlyAccount;
|
||||
pub use requests::{
|
||||
|
@ -55,4 +56,4 @@ pub use requests::{
|
|||
OutgoingVerificationRequest, RoomMessageRequest, ToDeviceRequest,
|
||||
};
|
||||
pub use store::CryptoStoreError;
|
||||
pub use verification::{AcceptSettings, Sas, VerificationRequest};
|
||||
pub use verification::{AcceptSettings, QrVerification, Sas, Verification, VerificationRequest};
|
||||
|
|
|
@ -379,7 +379,7 @@ impl OlmMachine {
|
|||
|
||||
*identity = id;
|
||||
|
||||
let public = identity.as_public_identity().await.expect(
|
||||
let public = identity.to_public_identity().await.expect(
|
||||
"Couldn't create a public version of the identity from a new private identity",
|
||||
);
|
||||
|
||||
|
|
|
@ -33,8 +33,8 @@ use serde::{Deserialize, Serialize};
|
|||
use serde_json::Error as JsonError;
|
||||
|
||||
use crate::{
|
||||
error::SignatureError, requests::UploadSigningKeysRequest, OwnUserIdentity, ReadOnlyAccount,
|
||||
ReadOnlyDevice, UserIdentity,
|
||||
error::SignatureError, identities::MasterPubkey, requests::UploadSigningKeysRequest,
|
||||
OwnUserIdentity, ReadOnlyAccount, ReadOnlyDevice, UserIdentity,
|
||||
};
|
||||
|
||||
/// Private cross signing identity.
|
||||
|
@ -91,6 +91,21 @@ impl PrivateCrossSigningIdentity {
|
|||
!(has_master && has_user && has_self)
|
||||
}
|
||||
|
||||
/// Can we sign our own devices, i.e. do we have a self signing key.
|
||||
pub async fn can_sign_devices(&self) -> bool {
|
||||
self.self_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 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())
|
||||
}
|
||||
|
||||
/// Create a new empty identity.
|
||||
pub(crate) fn empty(user_id: UserId) -> Self {
|
||||
Self {
|
||||
|
@ -102,7 +117,7 @@ impl PrivateCrossSigningIdentity {
|
|||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn as_public_identity(&self) -> Result<OwnUserIdentity, SignatureError> {
|
||||
pub(crate) async fn to_public_identity(&self) -> Result<OwnUserIdentity, SignatureError> {
|
||||
let master = self
|
||||
.master_key
|
||||
.lock()
|
||||
|
|
|
@ -146,11 +146,6 @@ impl GroupSessionManager {
|
|||
let mut changes = Changes::default();
|
||||
changes.outbound_group_sessions.push(s.clone());
|
||||
self.store.save_changes(changes).await?;
|
||||
} else {
|
||||
trace!(
|
||||
request_id = request_id.to_string().as_str(),
|
||||
"Marking room key share request as sent but session found that owns the given id"
|
||||
)
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
|
|
@ -239,7 +239,7 @@ impl Deref for Store {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
#[derive(Debug, Error)]
|
||||
/// The crypto store's error type.
|
||||
pub enum CryptoStoreError {
|
||||
/// The account that owns the sessions, group sessions, and devices wasn't
|
||||
|
|
|
@ -19,7 +19,7 @@ use matrix_sdk_common::uuid::Uuid;
|
|||
use ruma::{DeviceId, UserId};
|
||||
|
||||
use super::{event_enums::OutgoingContent, sas::content_to_request, Sas, Verification};
|
||||
use crate::{OutgoingRequest, RoomMessageRequest};
|
||||
use crate::{OutgoingRequest, QrVerification, RoomMessageRequest};
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct VerificationCache {
|
||||
|
@ -51,6 +51,20 @@ impl VerificationCache {
|
|||
self.insert(sas);
|
||||
}
|
||||
|
||||
pub fn insert_qr(&self, qr: QrVerification) {
|
||||
self.insert(qr)
|
||||
}
|
||||
|
||||
pub fn get_qr(&self, sender: &UserId, flow_id: &str) -> Option<QrVerification> {
|
||||
self.get(sender, flow_id).and_then(|v| {
|
||||
if let Verification::QrV1(qr) = v {
|
||||
Some(qr)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get(&self, sender: &UserId, flow_id: &str) -> Option<Verification> {
|
||||
self.verification.get(sender).and_then(|m| m.get(flow_id).map(|v| v.clone()))
|
||||
}
|
||||
|
@ -92,7 +106,7 @@ impl VerificationCache {
|
|||
pub fn get_sas(&self, user_id: &UserId, flow_id: &str) -> Option<Sas> {
|
||||
self.get(user_id, flow_id).and_then(|v| {
|
||||
if let Verification::SasV1(sas) = v {
|
||||
Some(sas.clone())
|
||||
Some(sas)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
|
|
|
@ -340,6 +340,17 @@ impl VerificationMachine {
|
|||
self.mark_sas_as_done(sas, content).await?;
|
||||
}
|
||||
}
|
||||
Some(Verification::QrV1(qr)) => {
|
||||
let (cancellation, request) = qr.receive_done(&c).await?;
|
||||
|
||||
if let Some(c) = cancellation {
|
||||
self.verifications.add_request(c.into())
|
||||
}
|
||||
|
||||
if let Some(s) = request {
|
||||
self.verifications.add_request(s.into())
|
||||
}
|
||||
}
|
||||
None => (),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,11 +12,10 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#![allow(missing_docs)]
|
||||
|
||||
mod cache;
|
||||
mod event_enums;
|
||||
mod machine;
|
||||
mod qrcode;
|
||||
mod requests;
|
||||
mod sas;
|
||||
|
||||
|
@ -24,6 +23,7 @@ use std::sync::Arc;
|
|||
|
||||
use event_enums::OutgoingContent;
|
||||
pub use machine::VerificationMachine;
|
||||
pub use qrcode::QrVerification;
|
||||
pub use requests::VerificationRequest;
|
||||
use ruma::{
|
||||
api::client::r0::keys::upload_signatures::Request as SignatureUploadRequest,
|
||||
|
@ -47,18 +47,25 @@ use crate::{
|
|||
CryptoStoreError, LocalTrust, ReadOnlyDevice, UserIdentities,
|
||||
};
|
||||
|
||||
/// An enum over the different verification types the SDK supports.
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum Verification {
|
||||
/// The `m.sas.v1` verification variant.
|
||||
SasV1(Sas),
|
||||
/// The `m.qr_code.*.v1` verification variant.
|
||||
QrV1(QrVerification),
|
||||
}
|
||||
|
||||
impl Verification {
|
||||
/// Has this verification finished.
|
||||
pub fn is_done(&self) -> bool {
|
||||
match self {
|
||||
Verification::SasV1(s) => s.is_done(),
|
||||
Verification::QrV1(qr) => qr.is_done(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Try to deconstruct this verification enum into a SAS verification.
|
||||
pub fn sas_v1(self) -> Option<Sas> {
|
||||
if let Verification::SasV1(sas) = self {
|
||||
Some(sas)
|
||||
|
@ -67,21 +74,52 @@ impl Verification {
|
|||
}
|
||||
}
|
||||
|
||||
/// 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
|
||||
}
|
||||
}
|
||||
|
||||
/// 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(),
|
||||
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(),
|
||||
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(),
|
||||
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(),
|
||||
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(),
|
||||
Verification::QrV1(v) => v.is_self_verification(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -92,6 +130,12 @@ impl From<Sas> for Verification {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<QrVerification> for Verification {
|
||||
fn from(qr: QrVerification) -> Self {
|
||||
Self::QrV1(qr)
|
||||
}
|
||||
}
|
||||
|
||||
/// The verification state indicating that the verification finished
|
||||
/// successfully.
|
||||
///
|
||||
|
@ -236,6 +280,10 @@ pub struct IdentitiesBeingVerified {
|
|||
}
|
||||
|
||||
impl IdentitiesBeingVerified {
|
||||
async fn can_sign_devices(&self) -> bool {
|
||||
self.private_identity.can_sign_devices().await
|
||||
}
|
||||
|
||||
fn user_id(&self) -> &UserId {
|
||||
self.private_identity.user_id()
|
||||
}
|
||||
|
|
|
@ -0,0 +1,886 @@
|
|||
// Copyright 2021 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.
|
||||
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use matrix_qrcode::{
|
||||
qrcode::QrCode, EncodingError, QrVerificationData, SelfVerificationData,
|
||||
SelfVerificationNoMasterKey, VerificationData,
|
||||
};
|
||||
use matrix_sdk_common::uuid::Uuid;
|
||||
use ruma::{
|
||||
api::client::r0::keys::upload_signatures::Request as SignatureUploadRequest,
|
||||
events::{
|
||||
key::verification::{
|
||||
cancel::CancelCode,
|
||||
done::{DoneEventContent, DoneToDeviceEventContent},
|
||||
start::{
|
||||
self, ReciprocateV1Content, StartEventContent, StartMethod,
|
||||
StartToDeviceEventContent,
|
||||
},
|
||||
Relation,
|
||||
},
|
||||
AnyMessageEventContent, AnyToDeviceEventContent,
|
||||
},
|
||||
identifiers::{DeviceIdBox, DeviceKeyAlgorithm, UserId},
|
||||
};
|
||||
use thiserror::Error;
|
||||
|
||||
use super::{
|
||||
event_enums::{DoneContent, OutgoingContent, OwnedStartContent, StartContent},
|
||||
sas::content_to_request,
|
||||
Cancelled, Done, FlowId, IdentitiesBeingVerified, VerificationResult,
|
||||
};
|
||||
use crate::{
|
||||
olm::{PrivateCrossSigningIdentity, ReadOnlyAccount},
|
||||
store::CryptoStore,
|
||||
CryptoStoreError, OutgoingVerificationRequest, ReadOnlyDevice, RoomMessageRequest,
|
||||
UserIdentities,
|
||||
};
|
||||
|
||||
const SECRET_SIZE: usize = 16;
|
||||
|
||||
/// An error for the different failure modes that can happen during the
|
||||
/// validation of a scanned QR code.
|
||||
#[derive(Debug, Error)]
|
||||
pub enum ScanError {
|
||||
/// An IO error inside the crypto store happened during the validation of
|
||||
/// the QR code scan.
|
||||
#[error(transparent)]
|
||||
Store(#[from] CryptoStoreError),
|
||||
/// A key mismatch happened during the validation of the QR code scan.
|
||||
#[error("The keys that are being verified didn't match (expected {expected}, found {found})")]
|
||||
KeyMismatch { expected: String, found: String },
|
||||
/// One of the users that is participating in this verification doesn't have
|
||||
/// a valid cross signing identity.
|
||||
#[error("The user {0} is missing a valid cross signing identity")]
|
||||
MissingCrossSigningIdentity(UserId),
|
||||
/// The device of the user that is participating in this verification
|
||||
/// doesn't have a valid device key.
|
||||
#[error("The user's {0} device {1} is not E2E capable")]
|
||||
MissingDeviceKeys(UserId, DeviceIdBox),
|
||||
/// The ID uniquely identifying this verification flow didn't match to the
|
||||
/// one that has been scanned.
|
||||
#[error("The unique verification flow id did not match (expected {expected}, found {found})")]
|
||||
FlowIdMismatch { expected: String, found: String },
|
||||
}
|
||||
|
||||
/// An object controlling QR code style key verification flows.
|
||||
#[derive(Clone)]
|
||||
pub struct QrVerification {
|
||||
flow_id: FlowId,
|
||||
store: Arc<dyn CryptoStore>,
|
||||
inner: Arc<QrVerificationData>,
|
||||
state: Arc<Mutex<InnerState>>,
|
||||
identities: IdentitiesBeingVerified,
|
||||
}
|
||||
|
||||
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.as_ref())
|
||||
.field("state", &self.state.lock().unwrap())
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl QrVerification {
|
||||
/// Has the QR verification been scanned by the other side.
|
||||
///
|
||||
/// When the verification object is in this state it's required that the
|
||||
/// user confirms that the other side has scanned the QR code.
|
||||
pub fn is_scanned(&self) -> bool {
|
||||
matches!(&*self.state.lock().unwrap(), InnerState::Scanned(_))
|
||||
}
|
||||
|
||||
/// Get our own user id.
|
||||
pub fn user_id(&self) -> &UserId {
|
||||
self.identities.user_id()
|
||||
}
|
||||
|
||||
/// Get the user id of the other user that is participating in this
|
||||
/// verification flow.
|
||||
pub fn other_user_id(&self) -> &UserId {
|
||||
self.identities.other_user_id()
|
||||
}
|
||||
|
||||
/// Has the verification flow completed.
|
||||
pub fn is_done(&self) -> bool {
|
||||
matches!(&*self.state.lock().unwrap(), InnerState::Done(_))
|
||||
}
|
||||
|
||||
/// Has the verification flow been cancelled.
|
||||
pub fn is_cancelled(&self) -> bool {
|
||||
matches!(&*self.state.lock().unwrap(), InnerState::Cancelled(_))
|
||||
}
|
||||
|
||||
/// Is this a verification that is veryfying one of our own devices
|
||||
pub fn is_self_verification(&self) -> bool {
|
||||
self.identities.is_self_verification()
|
||||
}
|
||||
|
||||
/// Get the unique ID that identifies this QR code verification flow.
|
||||
pub fn flow_id(&self) -> &FlowId {
|
||||
&self.flow_id
|
||||
}
|
||||
|
||||
/// Generate a QR code object that is representing this verification flow.
|
||||
///
|
||||
/// The `QrCode` can then be rendered as an image or as an unicode string.
|
||||
///
|
||||
/// The [`to_bytes()`](#method.to_bytes) method can be used to instead
|
||||
/// output the raw bytes that should be encoded as a QR code.
|
||||
pub fn to_qr_code(&self) -> Result<QrCode, EncodingError> {
|
||||
self.inner.to_qr_code()
|
||||
}
|
||||
|
||||
/// Generate a the raw bytes that should be encoded as a QR code is
|
||||
/// representing this verification flow.
|
||||
///
|
||||
/// The [`to_qr_code()`](#method.to_qr_code) method can be used to instead
|
||||
/// output a `QrCode` object that can be rendered.
|
||||
pub fn to_bytes(&self) -> Result<Vec<u8>, EncodingError> {
|
||||
self.inner.to_bytes()
|
||||
}
|
||||
|
||||
/// Cancel the verification flow.
|
||||
pub fn cancel(&self) -> Option<OutgoingVerificationRequest> {
|
||||
self.cancel_with_code(CancelCode::User).map(|c| self.content_to_request(c))
|
||||
}
|
||||
|
||||
/// Notify the other side that we have successfully scanned the QR code and
|
||||
/// that the QR verification flow can start.
|
||||
///
|
||||
/// This will return some `OutgoingContent` if the object is in the correct
|
||||
/// state to start the verification flow, otherwise `None`.
|
||||
pub fn reciprocate(&self) -> Option<OutgoingContent> {
|
||||
match &*self.state.lock().unwrap() {
|
||||
InnerState::Reciprocated(s) => Some(s.as_content(self.flow_id())),
|
||||
InnerState::Created(_)
|
||||
| InnerState::Scanned(_)
|
||||
| InnerState::Confirmed(_)
|
||||
| InnerState::Done(_)
|
||||
| InnerState::Cancelled(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Confirm that the other side has scanned our QR code.
|
||||
pub fn confirm_scanning(&self) -> Option<OutgoingVerificationRequest> {
|
||||
let mut state = self.state.lock().unwrap();
|
||||
|
||||
match &*state {
|
||||
InnerState::Scanned(s) => {
|
||||
let new_state = s.clone().confirm_scanning();
|
||||
let content = new_state.as_content(&self.flow_id);
|
||||
*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: Uuid::new_v4(), content }.into()
|
||||
}
|
||||
OutgoingContent::ToDevice(c) => content_to_request(
|
||||
self.identities.other_user_id(),
|
||||
self.identities.other_device_id().to_owned(),
|
||||
c,
|
||||
)
|
||||
.into(),
|
||||
}
|
||||
}
|
||||
|
||||
fn cancel_with_code(&self, code: CancelCode) -> Option<OutgoingContent> {
|
||||
let new_state = QrState::<Cancelled>::new(code);
|
||||
let content = new_state.as_content(self.flow_id());
|
||||
|
||||
let mut state = self.state.lock().unwrap();
|
||||
|
||||
match &*state {
|
||||
InnerState::Confirmed(_)
|
||||
| InnerState::Created(_)
|
||||
| InnerState::Scanned(_)
|
||||
| InnerState::Reciprocated(_)
|
||||
| InnerState::Done(_) => {
|
||||
*state = InnerState::Cancelled(new_state);
|
||||
Some(content)
|
||||
}
|
||||
InnerState::Cancelled(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
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(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.lock().unwrap() = 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.lock().unwrap()).clone();
|
||||
|
||||
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.lock().unwrap();
|
||||
|
||||
match &*state {
|
||||
InnerState::Created(s) => match s.clone().receive_reciprocate(content) {
|
||||
Ok(s) => {
|
||||
*state = InnerState::Scanned(s);
|
||||
None
|
||||
}
|
||||
Err(s) => {
|
||||
let content = s.as_content(self.flow_id());
|
||||
*state = InnerState::Cancelled(s);
|
||||
Some(self.content_to_request(content))
|
||||
}
|
||||
},
|
||||
InnerState::Confirmed(_)
|
||||
| InnerState::Scanned(_)
|
||||
| InnerState::Reciprocated(_)
|
||||
| InnerState::Done(_)
|
||||
| InnerState::Cancelled(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn generate_secret() -> String {
|
||||
let mut shared_secret = [0u8; SECRET_SIZE];
|
||||
getrandom::getrandom(&mut shared_secret)
|
||||
.expect("Can't generate randomness for the shared secret");
|
||||
crate::utilities::encode(shared_secret)
|
||||
}
|
||||
|
||||
pub(crate) fn new_self(
|
||||
store: Arc<dyn CryptoStore>,
|
||||
flow_id: FlowId,
|
||||
own_master_key: String,
|
||||
other_device_key: String,
|
||||
identities: IdentitiesBeingVerified,
|
||||
) -> 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(store, flow_id, inner, identities)
|
||||
}
|
||||
|
||||
pub(crate) fn new_self_no_master(
|
||||
account: ReadOnlyAccount,
|
||||
store: Arc<dyn CryptoStore>,
|
||||
flow_id: FlowId,
|
||||
own_master_key: String,
|
||||
identities: IdentitiesBeingVerified,
|
||||
) -> QrVerification {
|
||||
let secret = Self::generate_secret();
|
||||
|
||||
let inner: QrVerificationData = SelfVerificationNoMasterKey::new(
|
||||
flow_id.as_str().to_owned(),
|
||||
account.identity_keys().ed25519().to_string(),
|
||||
own_master_key,
|
||||
secret,
|
||||
)
|
||||
.into();
|
||||
|
||||
Self::new_helper(store, flow_id, inner, identities)
|
||||
}
|
||||
|
||||
pub(crate) fn new_cross(
|
||||
store: Arc<dyn CryptoStore>,
|
||||
flow_id: FlowId,
|
||||
own_master_key: String,
|
||||
other_master_key: String,
|
||||
identities: IdentitiesBeingVerified,
|
||||
) -> Self {
|
||||
let secret = Self::generate_secret();
|
||||
|
||||
let event_id = if let FlowId::InRoom(_, e) = &flow_id {
|
||||
e.to_owned()
|
||||
} else {
|
||||
panic!("A verification between users is only valid in a room");
|
||||
};
|
||||
|
||||
let inner: QrVerificationData =
|
||||
VerificationData::new(event_id, own_master_key, other_master_key, secret).into();
|
||||
|
||||
Self::new_helper(store, flow_id, inner, identities)
|
||||
}
|
||||
|
||||
pub(crate) async fn from_scan(
|
||||
store: Arc<dyn CryptoStore>,
|
||||
own_account: ReadOnlyAccount,
|
||||
private_identity: PrivateCrossSigningIdentity,
|
||||
other_user_id: UserId,
|
||||
other_device_id: DeviceIdBox,
|
||||
flow_id: FlowId,
|
||||
qr_code: QrVerificationData,
|
||||
) -> 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 own_identity =
|
||||
store.get_user_identity(own_account.user_id()).await?.ok_or_else(|| {
|
||||
ScanError::MissingCrossSigningIdentity(own_account.user_id().to_owned())
|
||||
})?;
|
||||
let other_identity = store
|
||||
.get_user_identity(&other_user_id)
|
||||
.await?
|
||||
.ok_or_else(|| ScanError::MissingCrossSigningIdentity(other_user_id.clone()))?;
|
||||
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 check_master_key = |key, identity: &UserIdentities| {
|
||||
let master_key = identity.master_key().get_first_key().ok_or_else(|| {
|
||||
ScanError::MissingCrossSigningIdentity(identity.user_id().clone())
|
||||
})?;
|
||||
|
||||
if key != master_key {
|
||||
Err(ScanError::KeyMismatch {
|
||||
expected: master_key.to_owned(),
|
||||
found: qr_code.first_key().to_owned(),
|
||||
})
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
};
|
||||
|
||||
let identities = 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),
|
||||
}
|
||||
}
|
||||
QrVerificationData::SelfVerification(_) => {
|
||||
check_master_key(qr_code.first_key(), &other_identity)?;
|
||||
if qr_code.second_key() != own_account.identity_keys().ed25519() {
|
||||
return Err(ScanError::KeyMismatch {
|
||||
expected: own_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),
|
||||
}
|
||||
}
|
||||
QrVerificationData::SelfVerificationNoMasterKey(_) => {
|
||||
let device_key =
|
||||
other_device.get_key(DeviceKeyAlgorithm::Ed25519).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_owned(),
|
||||
found: qr_code.first_key().to_owned(),
|
||||
});
|
||||
}
|
||||
check_master_key(qr_code.second_key(), &other_identity)?;
|
||||
IdentitiesBeingVerified {
|
||||
private_identity,
|
||||
store: store.clone(),
|
||||
device_being_verified: other_device,
|
||||
identity_being_verified: None,
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let secret = qr_code.secret().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() },
|
||||
}))
|
||||
.into(),
|
||||
identities,
|
||||
})
|
||||
}
|
||||
|
||||
fn new_helper(
|
||||
store: Arc<dyn CryptoStore>,
|
||||
flow_id: FlowId,
|
||||
inner: QrVerificationData,
|
||||
identities: IdentitiesBeingVerified,
|
||||
) -> Self {
|
||||
let secret = inner.secret().to_owned();
|
||||
|
||||
Self {
|
||||
store,
|
||||
flow_id,
|
||||
inner: inner.into(),
|
||||
state: Mutex::new(InnerState::Created(QrState { state: Created { secret } })).into(),
|
||||
identities,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[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,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
struct Created {
|
||||
secret: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
struct Scanned {}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
struct Confirmed {}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
struct Reciprocated {
|
||||
own_device_id: DeviceIdBox,
|
||||
secret: String,
|
||||
}
|
||||
|
||||
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) => {
|
||||
StartToDeviceEventContent::new(self.own_device_id.clone(), t.clone(), method).into()
|
||||
}
|
||||
FlowId::InRoom(r, e) => (
|
||||
r.clone(),
|
||||
StartEventContent::new(
|
||||
self.own_device_id.clone(),
|
||||
method,
|
||||
Relation::new(e.clone()),
|
||||
),
|
||||
)
|
||||
.into(),
|
||||
};
|
||||
|
||||
content.into()
|
||||
}
|
||||
}
|
||||
|
||||
impl QrState<Scanned> {
|
||||
fn confirm_scanning(self) -> QrState<Confirmed> {
|
||||
QrState { state: Confirmed {} }
|
||||
}
|
||||
}
|
||||
|
||||
impl QrState<Cancelled> {
|
||||
fn new(cancel_code: CancelCode) -> Self {
|
||||
QrState { state: Cancelled::new(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() {
|
||||
start::StartMethod::ReciprocateV1(m) => {
|
||||
// TODO use constant time eq here.
|
||||
if self.state.secret == m.secret {
|
||||
Ok(QrState { state: Scanned {} })
|
||||
} else {
|
||||
Err(QrState::<Cancelled>::new(CancelCode::KeyMismatch))
|
||||
}
|
||||
}
|
||||
_ => Err(QrState::<Cancelled>::new(CancelCode::UnknownMethod)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl QrState<Done> {
|
||||
fn as_content(&self, flow_id: &FlowId) -> OutgoingContent {
|
||||
self.state.as_content(flow_id)
|
||||
}
|
||||
|
||||
fn verified_identities(&self) -> (Arc<[ReadOnlyDevice]>, Arc<[UserIdentities]>) {
|
||||
(self.state.verified_devices.clone(), self.state.verified_master_keys.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl QrState<Confirmed> {
|
||||
fn into_done(
|
||||
self,
|
||||
_: &DoneContent,
|
||||
verified_device: Option<&ReadOnlyDevice>,
|
||||
verified_identity: Option<&UserIdentities>,
|
||||
) -> 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(
|
||||
DoneToDeviceEventContent::new(t.to_owned()),
|
||||
)
|
||||
.into(),
|
||||
FlowId::InRoom(r, e) => (
|
||||
r.to_owned(),
|
||||
AnyMessageEventContent::KeyVerificationDone(DoneEventContent::new(Relation::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<&ReadOnlyDevice>,
|
||||
verified_identity: Option<&UserIdentities>,
|
||||
) -> 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 test {
|
||||
use std::{convert::TryFrom, sync::Arc};
|
||||
|
||||
use matrix_qrcode::QrVerificationData;
|
||||
use matrix_sdk_test::async_test;
|
||||
use ruma::identifiers::{event_id, room_id, user_id, DeviceIdBox, UserId};
|
||||
|
||||
use crate::{
|
||||
olm::{PrivateCrossSigningIdentity, ReadOnlyAccount},
|
||||
store::{Changes, CryptoStore, MemoryStore},
|
||||
verification::{
|
||||
event_enums::{DoneContent, OutgoingContent, StartContent},
|
||||
FlowId, IdentitiesBeingVerified,
|
||||
},
|
||||
QrVerification, ReadOnlyDevice,
|
||||
};
|
||||
|
||||
fn user_id() -> UserId {
|
||||
user_id!("@example:localhost")
|
||||
}
|
||||
|
||||
fn memory_store() -> Arc<dyn CryptoStore> {
|
||||
Arc::new(MemoryStore::new())
|
||||
}
|
||||
|
||||
fn device_id() -> DeviceIdBox {
|
||||
"DEVICEID".into()
|
||||
}
|
||||
|
||||
#[async_test]
|
||||
async fn test_verification_creation() {
|
||||
let store = memory_store();
|
||||
|
||||
let account = ReadOnlyAccount::new(&user_id(), &device_id());
|
||||
let private_identity = PrivateCrossSigningIdentity::new(user_id()).await;
|
||||
let flow_id = FlowId::ToDevice("test_transaction".to_owned());
|
||||
|
||||
let device_key = account.identity_keys().ed25519().to_owned();
|
||||
let master_key = private_identity.master_public_key().await.unwrap();
|
||||
let master_key = master_key.get_first_key().unwrap().to_owned();
|
||||
|
||||
let alice_device = ReadOnlyDevice::from_account(&account).await;
|
||||
|
||||
let identities = IdentitiesBeingVerified {
|
||||
private_identity,
|
||||
store: store.clone(),
|
||||
device_being_verified: alice_device,
|
||||
identity_being_verified: None,
|
||||
};
|
||||
|
||||
let verification = QrVerification::new_self_no_master(
|
||||
account,
|
||||
store.clone(),
|
||||
flow_id.clone(),
|
||||
master_key.clone(),
|
||||
identities.clone(),
|
||||
);
|
||||
|
||||
assert_eq!(verification.inner.first_key(), &device_key);
|
||||
assert_eq!(verification.inner.second_key(), &master_key);
|
||||
|
||||
let verification = QrVerification::new_self(
|
||||
store.clone(),
|
||||
flow_id,
|
||||
master_key.clone(),
|
||||
device_key.clone(),
|
||||
identities.clone(),
|
||||
);
|
||||
|
||||
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")).await;
|
||||
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"), event_id!("$EVENTID"));
|
||||
|
||||
let verification = QrVerification::new_cross(
|
||||
store.clone(),
|
||||
flow_id,
|
||||
master_key.clone(),
|
||||
bob_master_key.clone(),
|
||||
identities,
|
||||
);
|
||||
|
||||
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 = ReadOnlyAccount::new(&user_id(), &device_id());
|
||||
let store = memory_store();
|
||||
|
||||
let bob_account = ReadOnlyAccount::new(alice_account.user_id(), "BOBDEVICE".into());
|
||||
|
||||
let private_identity = PrivateCrossSigningIdentity::new(user_id()).await;
|
||||
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 = ReadOnlyDevice::from_account(&alice_account).await;
|
||||
let bob_device = ReadOnlyDevice::from_account(&bob_account).await;
|
||||
|
||||
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 = IdentitiesBeingVerified {
|
||||
private_identity: PrivateCrossSigningIdentity::empty(
|
||||
alice_account.user_id().to_owned(),
|
||||
),
|
||||
store: store.clone(),
|
||||
device_being_verified: alice_device.clone(),
|
||||
identity_being_verified: Some(identity.clone().into()),
|
||||
};
|
||||
|
||||
let alice_verification = QrVerification::new_self_no_master(
|
||||
alice_account.clone(),
|
||||
store,
|
||||
flow_id.clone(),
|
||||
master_key.clone(),
|
||||
identities,
|
||||
);
|
||||
|
||||
let bob_store = memory_store();
|
||||
|
||||
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,
|
||||
bob_account,
|
||||
private_identity,
|
||||
alice_account.user_id().to_owned(),
|
||||
alice_account.device_id().to_owned(),
|
||||
flow_id,
|
||||
qr_code,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let content = bob_verification.reciprocate().unwrap();
|
||||
let content = StartContent::try_from(&content).unwrap();
|
||||
|
||||
alice_verification.receive_reciprocation(&content);
|
||||
|
||||
let request = alice_verification.confirm_scanning().unwrap();
|
||||
let content = OutgoingContent::from(request);
|
||||
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::from(request.unwrap());
|
||||
let content = DoneContent::try_from(&content).unwrap();
|
||||
alice_verification.receive_done(&content).await.unwrap();
|
||||
|
||||
assert!(alice_verification.is_done());
|
||||
assert!(bob_verification.is_done());
|
||||
|
||||
let identity = alice_verification
|
||||
.store
|
||||
.get_user_identity(alice_account.user_id())
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
|
||||
let identity = identity.own().unwrap();
|
||||
|
||||
assert!(!bob_device.is_trusted());
|
||||
assert!(alice_device.is_trusted());
|
||||
assert!(identity.is_verified());
|
||||
};
|
||||
|
||||
let flow_id = FlowId::ToDevice("test_transaction".to_owned());
|
||||
test(flow_id).await;
|
||||
|
||||
let flow_id = FlowId::InRoom(room_id!("!test:example"), event_id!("$EVENTID"));
|
||||
test(flow_id).await;
|
||||
}
|
||||
}
|
|
@ -16,6 +16,7 @@
|
|||
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use matrix_qrcode::QrVerificationData;
|
||||
use matrix_sdk_common::uuid::Uuid;
|
||||
use ruma::{
|
||||
api::client::r0::to_device::DeviceIdOrAllDevices,
|
||||
|
@ -30,17 +31,18 @@ use ruma::{
|
|||
room::message::KeyVerificationRequestEventContent,
|
||||
AnyMessageEventContent, AnyToDeviceEventContent,
|
||||
},
|
||||
DeviceId, DeviceIdBox, EventId, MilliSecondsSinceUnixEpoch, RoomId, UserId,
|
||||
DeviceId, DeviceIdBox, DeviceKeyAlgorithm, EventId, MilliSecondsSinceUnixEpoch, RoomId, UserId,
|
||||
};
|
||||
use tracing::{info, warn};
|
||||
use tracing::{info, trace, warn};
|
||||
|
||||
use super::{
|
||||
cache::VerificationCache,
|
||||
event_enums::{
|
||||
CancelContent, DoneContent, OutgoingContent, ReadyContent, RequestContent, StartContent,
|
||||
},
|
||||
qrcode::{QrVerification, ScanError},
|
||||
sas::content_to_request,
|
||||
Cancelled, FlowId,
|
||||
Cancelled, FlowId, IdentitiesBeingVerified,
|
||||
};
|
||||
use crate::{
|
||||
olm::{PrivateCrossSigningIdentity, ReadOnlyAccount},
|
||||
|
@ -49,10 +51,20 @@ use crate::{
|
|||
ToDeviceRequest, UserIdentities,
|
||||
};
|
||||
|
||||
const SUPPORTED_METHODS: &[VerificationMethod] = &[VerificationMethod::MSasV1];
|
||||
const SUPPORTED_METHODS: &[VerificationMethod] = &[
|
||||
VerificationMethod::MSasV1,
|
||||
VerificationMethod::MQrCodeShowV1,
|
||||
VerificationMethod::MReciprocateV1,
|
||||
];
|
||||
|
||||
/// An object controlling key verification requests.
|
||||
///
|
||||
/// Interactive verification flows usually start with a verification request,
|
||||
/// this object lets you send and reply to such a verification request.
|
||||
///
|
||||
/// After the initial handshake the verification flow transitions into one of
|
||||
/// the verification methods.
|
||||
#[derive(Clone, Debug)]
|
||||
/// TODO
|
||||
pub struct VerificationRequest {
|
||||
verification_cache: VerificationCache,
|
||||
account: ReadOnlyAccount,
|
||||
|
@ -62,7 +74,6 @@ pub struct VerificationRequest {
|
|||
}
|
||||
|
||||
impl VerificationRequest {
|
||||
/// TODO
|
||||
pub(crate) fn new(
|
||||
cache: VerificationCache,
|
||||
account: ReadOnlyAccount,
|
||||
|
@ -93,7 +104,6 @@ impl VerificationRequest {
|
|||
}
|
||||
}
|
||||
|
||||
/// TODO
|
||||
pub(crate) fn new_to_device(
|
||||
cache: VerificationCache,
|
||||
account: ReadOnlyAccount,
|
||||
|
@ -122,7 +132,10 @@ impl VerificationRequest {
|
|||
}
|
||||
}
|
||||
|
||||
/// TODO
|
||||
/// Create an event content that can be sent as a to-device event to request
|
||||
/// verification from the other side. This should be used only for
|
||||
/// self-verifications and it should be sent to the specific device that we
|
||||
/// want to verify.
|
||||
pub fn request_to_device(&self) -> RequestToDeviceEventContent {
|
||||
RequestToDeviceEventContent::new(
|
||||
self.account.device_id().into(),
|
||||
|
@ -132,7 +145,10 @@ impl VerificationRequest {
|
|||
)
|
||||
}
|
||||
|
||||
/// TODO
|
||||
/// Create an event content that can be sent as a room event to request
|
||||
/// verification from the other side. This should be used only for
|
||||
/// verifications of other users and it should be sent to a room we consider
|
||||
/// to be a DM with the other user.
|
||||
pub fn request(
|
||||
own_user_id: &UserId,
|
||||
own_device_id: &DeviceId,
|
||||
|
@ -157,11 +173,21 @@ impl VerificationRequest {
|
|||
&self.other_user_id
|
||||
}
|
||||
|
||||
/// Is the verification request ready to start a verification flow.
|
||||
pub fn is_ready(&self) -> bool {
|
||||
matches!(&*self.inner.lock().unwrap(), InnerRequest::Ready(_))
|
||||
}
|
||||
|
||||
/// Get the unique ID of this verification request
|
||||
pub fn flow_id(&self) -> &FlowId {
|
||||
&self.flow_id
|
||||
}
|
||||
|
||||
/// Is this a verification that is veryfying one of our own devices
|
||||
pub fn is_self_verification(&self) -> bool {
|
||||
self.account.user_id() == self.other_user()
|
||||
}
|
||||
|
||||
/// Has the verification flow that was started with this request finished.
|
||||
pub fn is_done(&self) -> bool {
|
||||
matches!(&*self.inner.lock().unwrap(), InnerRequest::Done(_))
|
||||
|
@ -173,6 +199,43 @@ impl VerificationRequest {
|
|||
matches!(&*self.inner.lock().unwrap(), InnerRequest::Cancelled(_))
|
||||
}
|
||||
|
||||
/// Generate a QR code that can be used by another client to start a QR code
|
||||
/// based verification.
|
||||
pub async fn generate_qr_code(&self) -> Result<Option<QrVerification>, CryptoStoreError> {
|
||||
self.inner.lock().unwrap().generate_qr_code().await
|
||||
}
|
||||
|
||||
/// Start a QR code verification by providing a scanned QR code for this
|
||||
/// verification flow.
|
||||
///
|
||||
/// Returns a `ScanError` if the QR code isn't valid, `None` if the
|
||||
/// verification request isn't in the ready state or we don't support QR
|
||||
/// code verification, otherwise a newly created `QrVerification` object
|
||||
/// which will be used for the remainder of the verification flow.
|
||||
pub async fn scan_qr_code(
|
||||
&self,
|
||||
data: QrVerificationData,
|
||||
) -> Result<Option<QrVerification>, ScanError> {
|
||||
let state = self.inner.lock().unwrap();
|
||||
|
||||
if let InnerRequest::Ready(r) = &*state {
|
||||
Ok(Some(
|
||||
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(),
|
||||
r.state.flow_id.clone(),
|
||||
data,
|
||||
)
|
||||
.await?,
|
||||
))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn from_request(
|
||||
cache: VerificationCache,
|
||||
account: ReadOnlyAccount,
|
||||
|
@ -258,11 +321,6 @@ impl VerificationRequest {
|
|||
}
|
||||
}
|
||||
|
||||
/// Is the verification request ready to start a verification flow.
|
||||
pub fn is_ready(&self) -> bool {
|
||||
matches!(&*self.inner.lock().unwrap(), InnerRequest::Ready(_))
|
||||
}
|
||||
|
||||
pub(crate) fn start(
|
||||
&self,
|
||||
device: ReadOnlyDevice,
|
||||
|
@ -357,6 +415,17 @@ impl InnerRequest {
|
|||
})
|
||||
}
|
||||
|
||||
async fn generate_qr_code(&self) -> Result<Option<QrVerification>, CryptoStoreError> {
|
||||
match self {
|
||||
InnerRequest::Created(_) => Ok(None),
|
||||
InnerRequest::Requested(_) => Ok(None),
|
||||
InnerRequest::Ready(s) => s.generate_qr_code().await,
|
||||
InnerRequest::Passive(_) => Ok(None),
|
||||
InnerRequest::Done(_) => Ok(None),
|
||||
InnerRequest::Cancelled(_) => Ok(None),
|
||||
}
|
||||
}
|
||||
|
||||
fn to_started_sas(
|
||||
&self,
|
||||
content: &StartContent,
|
||||
|
@ -570,6 +639,86 @@ impl RequestState<Ready> {
|
|||
)
|
||||
}
|
||||
|
||||
async fn generate_qr_code(&self) -> Result<Option<QrVerification>, CryptoStoreError> {
|
||||
// TODO return an error explaining why we can't generate a QR code?
|
||||
let device = if let Some(device) =
|
||||
self.store.get_device(&self.other_user_id, &self.state.other_device_id).await?
|
||||
{
|
||||
device
|
||||
} else {
|
||||
warn!(
|
||||
user_id = self.other_user_id.as_str(),
|
||||
device_id = self.state.other_device_id.as_str(),
|
||||
"Can't create a QR code, the device that accepted the \
|
||||
verification doesn't exist"
|
||||
);
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
let identites = IdentitiesBeingVerified {
|
||||
private_identity: self.private_cross_signing_identity.clone(),
|
||||
store: self.store.clone(),
|
||||
device_being_verified: device,
|
||||
identity_being_verified: self.store.get_user_identity(&self.other_user_id).await?,
|
||||
};
|
||||
|
||||
let verification = if let Some(identity) = &identites.identity_being_verified {
|
||||
match &identity {
|
||||
UserIdentities::Own(i) => {
|
||||
if identites.can_sign_devices().await {
|
||||
Some(QrVerification::new_self(
|
||||
self.store.clone(),
|
||||
self.flow_id.as_ref().to_owned(),
|
||||
i.master_key().get_first_key().unwrap().to_owned(),
|
||||
identites
|
||||
.other_device()
|
||||
.get_key(DeviceKeyAlgorithm::Ed25519)
|
||||
.unwrap()
|
||||
.to_owned(),
|
||||
identites,
|
||||
))
|
||||
} else {
|
||||
Some(QrVerification::new_self_no_master(
|
||||
self.account.clone(),
|
||||
self.store.clone(),
|
||||
self.flow_id.as_ref().to_owned(),
|
||||
i.master_key().get_first_key().unwrap().to_owned(),
|
||||
identites,
|
||||
))
|
||||
}
|
||||
}
|
||||
UserIdentities::Other(i) => Some(QrVerification::new_cross(
|
||||
self.store.clone(),
|
||||
self.flow_id.as_ref().to_owned(),
|
||||
self.private_cross_signing_identity
|
||||
.master_public_key()
|
||||
.await
|
||||
.unwrap()
|
||||
.get_first_key()
|
||||
.unwrap()
|
||||
.to_owned(),
|
||||
i.master_key().get_first_key().unwrap().to_owned(),
|
||||
identites,
|
||||
)),
|
||||
}
|
||||
} else {
|
||||
warn!(
|
||||
user_id = self.other_user_id.as_str(),
|
||||
device_id = self.state.other_device_id.as_str(),
|
||||
"Can't create a QR code, the user doesn't have a valid cross \
|
||||
signing identity."
|
||||
);
|
||||
|
||||
None
|
||||
};
|
||||
|
||||
if let Some(verification) = &verification {
|
||||
self.verification_cache.insert_qr(verification.clone());
|
||||
}
|
||||
|
||||
Ok(verification)
|
||||
}
|
||||
|
||||
async fn receive_start(
|
||||
&self,
|
||||
sender: &UserId,
|
||||
|
@ -597,6 +746,10 @@ impl RequestState<Ready> {
|
|||
|
||||
match content.method() {
|
||||
StartMethod::SasV1(_) => match self.to_started_sas(content, device.clone(), identity) {
|
||||
// TODO check if there is already a SAS verification, i.e. we
|
||||
// already started one before the other side tried to do the
|
||||
// same; ignore it if we did and we're the lexicographically
|
||||
// smaller user ID, otherwise auto-accept the newly started one.
|
||||
Ok(s) => {
|
||||
info!("Started a new SAS verification.");
|
||||
self.verification_cache.insert_sas(s);
|
||||
|
@ -615,6 +768,21 @@ impl RequestState<Ready> {
|
|||
)
|
||||
}
|
||||
},
|
||||
StartMethod::ReciprocateV1(_) => {
|
||||
if let Some(qr_verification) =
|
||||
self.verification_cache.get_qr(sender, content.flow_id())
|
||||
{
|
||||
if let Some(request) = qr_verification.receive_reciprocation(content) {
|
||||
self.verification_cache.add_request(request.into())
|
||||
}
|
||||
trace!(
|
||||
sender = device.user_id().as_str(),
|
||||
device_id = device.device_id().as_str(),
|
||||
verification =? qr_verification,
|
||||
"Received a QR code reciprocation"
|
||||
)
|
||||
}
|
||||
}
|
||||
m => {
|
||||
warn!(method =? m, "Received a key verification start event with an unsupported method")
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue