crypto: Add initial support for QR code verification

master
Damir Jelić 2021-06-09 16:30:15 +02:00
parent 71aba433da
commit 00c3921d2a
12 changed files with 1177 additions and 30 deletions

View File

@ -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"] }

View File

@ -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,

View File

@ -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};

View File

@ -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",
);

View File

@ -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()

View File

@ -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(())

View File

@ -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

View File

@ -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
}

View File

@ -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 => (),
}
}

View File

@ -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()
}

View File

@ -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;
}
}

View File

@ -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")
}