crypto: WIP change the types of the sas sturcts to allow in-room verifications.

master
Damir Jelić 2020-12-10 17:49:28 +01:00
parent 1bb5b42b1d
commit b0ac9d3320
8 changed files with 331 additions and 80 deletions

View File

@ -42,6 +42,7 @@ use tracing::warn;
use crate::{
olm::{InboundGroupSession, PrivateCrossSigningIdentity, Session},
store::{Changes, DeviceChanges},
OutgoingRequest, OutgoingRequests,
};
#[cfg(test)]
use crate::{OlmMachine, ReadOnlyAccount};
@ -91,9 +92,16 @@ impl Device {
///
/// Returns a `Sas` object and to-device request that needs to be sent out.
pub async fn start_verification(&self) -> StoreResult<(Sas, ToDeviceRequest)> {
self.verification_machine
let (sas, request) = self
.verification_machine
.start_sas(self.inner.clone())
.await
.await?;
if let OutgoingRequests::ToDeviceRequest(r) = request {
Ok((sas, r))
} else {
panic!("Invalid verification request type");
}
}
/// Get the Olm sessions that belong to this device.

View File

@ -30,14 +30,14 @@ use matrix_sdk_common::{
use super::{
requests::VerificationRequest,
sas::{content_to_request, Sas, VerificationResult},
sas::{content_to_request, OutgoingContent, Sas, VerificationResult},
};
use crate::{
olm::PrivateCrossSigningIdentity,
requests::{OutgoingRequest, ToDeviceRequest},
store::{CryptoStore, CryptoStoreError},
ReadOnlyAccount, ReadOnlyDevice,
OutgoingRequests, ReadOnlyAccount, ReadOnlyDevice,
};
#[derive(Clone, Debug)]
@ -46,6 +46,7 @@ pub struct VerificationMachine {
private_identity: Arc<Mutex<PrivateCrossSigningIdentity>>,
pub(crate) store: Arc<Box<dyn CryptoStore>>,
verifications: Arc<DashMap<String, Sas>>,
room_verifications: Arc<DashMap<String, Sas>>,
requests: Arc<DashMap<EventId, VerificationRequest>>,
outgoing_to_device_messages: Arc<DashMap<Uuid, OutgoingRequest>>,
}
@ -63,13 +64,14 @@ impl VerificationMachine {
verifications: DashMap::new().into(),
requests: DashMap::new().into(),
outgoing_to_device_messages: DashMap::new().into(),
room_verifications: DashMap::new().into(),
}
}
pub async fn start_sas(
&self,
device: ReadOnlyDevice,
) -> Result<(Sas, ToDeviceRequest), CryptoStoreError> {
) -> Result<(Sas, OutgoingRequests), CryptoStoreError> {
let identity = self.store.get_user_identity(device.user_id()).await?;
let private_identity = self.private_identity.lock().await.clone();
@ -81,14 +83,17 @@ impl VerificationMachine {
identity,
);
let request = content_to_request(
device.user_id(),
device.device_id(),
AnyToDeviceEventContent::KeyVerificationStart(content),
);
let request: OutgoingRequests = match content {
OutgoingContent::Room(c) => todo!(),
OutgoingContent::ToDevice(c) => {
let request = content_to_request(device.user_id(), device.device_id(), c);
self.verifications
.insert(sas.flow_id().to_string(), sas.clone());
self.verifications
.insert(sas.flow_id().to_string(), sas.clone());
request.into()
}
};
Ok((sas, request))
}
@ -170,9 +175,9 @@ impl VerificationMachine {
);
let request = VerificationRequest::from_request_event(
self.account.clone(),
self.store.clone(),
room_id,
self.account.user_id(),
self.account.device_id(),
&m.sender,
&m.event_id,
r,

View File

@ -19,26 +19,38 @@ use std::sync::{Arc, Mutex};
use matrix_sdk_common::{
api::r0::message::send_message_event::Response as RoomMessageResponse,
events::{
key::verification::{ready::ReadyEventContent, Relation, VerificationMethod},
key::verification::{
ready::ReadyEventContent, start::StartEventContent, Relation, VerificationMethod,
},
room::message::KeyVerificationRequestEventContent,
},
identifiers::{DeviceId, DeviceIdBox, EventId, RoomId, UserId},
};
use crate::{
olm::{PrivateCrossSigningIdentity, ReadOnlyAccount},
store::CryptoStore,
ReadOnlyDevice, Sas, UserIdentities, UserIdentity,
};
use super::sas::{OutgoingContent, StartContent};
const SUPPORTED_METHODS: &[VerificationMethod] = &[VerificationMethod::MSasV1];
#[derive(Clone, Debug)]
/// TODO
pub struct VerificationRequest {
inner: Arc<Mutex<InnerRequest>>,
account: ReadOnlyAccount,
store: Arc<Box<dyn CryptoStore>>,
room_id: Arc<RoomId>,
}
impl VerificationRequest {
pub(crate) fn from_request_event(
account: ReadOnlyAccount,
store: Arc<Box<dyn CryptoStore>>,
room_id: &RoomId,
own_user_id: &UserId,
own_device_id: &DeviceId,
sender: &UserId,
event_id: &EventId,
content: &KeyVerificationRequestEventContent,
@ -46,13 +58,15 @@ impl VerificationRequest {
Self {
inner: Arc::new(Mutex::new(InnerRequest::Requested(
RequestState::from_request_event(
own_user_id,
own_device_id,
account.user_id(),
account.device_id(),
sender,
event_id,
content,
),
))),
account,
store,
room_id: room_id.clone().into(),
}
}
@ -74,7 +88,6 @@ enum InnerRequest {
Sent(RequestState<Sent>),
Requested(RequestState<Requested>),
Ready(RequestState<Ready>),
Accepted(RequestState<Accepted>),
Passive(RequestState<Passive>),
}
@ -82,7 +95,7 @@ impl InnerRequest {
fn accept(&mut self) -> Option<ReadyEventContent> {
if let InnerRequest::Requested(s) = self {
let (state, content) = s.clone().accept();
*self = InnerRequest::Accepted(state);
*self = InnerRequest::Ready(state);
Some(content)
} else {
@ -200,13 +213,12 @@ impl RequestState<Requested> {
}
}
fn accept(self) -> (RequestState<Accepted>, ReadyEventContent) {
// TODO let the user pick a method here.
fn accept(self) -> (RequestState<Ready>, ReadyEventContent) {
let state = RequestState {
own_user_id: self.own_user_id,
own_device_id: self.own_device_id.clone(),
other_user_id: self.other_user_id,
state: Accepted {
state: Ready {
methods: self.state.methods.clone(),
other_device_id: self.state.other_device_id.clone(),
flow_id: self.state.flow_id.clone(),
@ -238,17 +250,34 @@ struct Ready {
pub flow_id: EventId,
}
#[derive(Clone, Debug)]
struct Accepted {
/// The verification methods that were accepted
pub methods: Vec<VerificationMethod>,
impl RequestState<Ready> {
fn into_started_sas(
self,
account: ReadOnlyAccount,
private_identity: PrivateCrossSigningIdentity,
other_device: ReadOnlyDevice,
other_identity: UserIdentity,
) -> Sas {
todo!()
// Sas::from_start_event(account, private_identity, other_device, other_identity, event)
}
/// The device id of the device that responded to the verification request.
pub other_device_id: DeviceIdBox,
/// The event id of the `m.key.verification.request` event which acts as an
/// unique id identifying this verification flow.
pub flow_id: EventId,
fn start_sas(
self,
store: Arc<Box<dyn CryptoStore>>,
account: ReadOnlyAccount,
private_identity: PrivateCrossSigningIdentity,
other_device: ReadOnlyDevice,
other_identity: Option<UserIdentities>,
) -> (Sas, OutgoingContent) {
Sas::start(
account,
private_identity,
other_device,
store,
other_identity,
)
}
}
#[derive(Clone, Debug)]

View File

@ -0,0 +1,76 @@
// Copyright 2020 The Matrix.org Foundation C.I.C.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#![allow(dead_code)]
use std::convert::TryInto;
use matrix_sdk_common::{
events::{
key::verification::start::{StartEventContent, StartToDeviceEventContent},
AnyMessageEventContent, AnyToDeviceEventContent, MessageEvent, ToDeviceEvent,
},
CanonicalJsonValue,
};
#[derive(Clone, Debug)]
pub enum StartContent {
ToDevice(StartToDeviceEventContent),
Room(StartEventContent),
}
impl StartContent {
pub fn to_canonical_json(self) -> CanonicalJsonValue {
let content = match self {
StartContent::Room(c) => serde_json::to_value(c),
StartContent::ToDevice(c) => serde_json::to_value(c),
};
content
.expect("Can't serialize content")
.try_into()
.expect("Can't canonicalize content")
}
}
impl From<StartEventContent> for StartContent {
fn from(content: StartEventContent) -> Self {
StartContent::Room(content)
}
}
impl From<StartToDeviceEventContent> for StartContent {
fn from(content: StartToDeviceEventContent) -> Self {
StartContent::ToDevice(content)
}
}
#[derive(Clone, Debug)]
pub enum OutgoingContent {
Room(AnyMessageEventContent),
ToDevice(AnyToDeviceEventContent),
}
impl From<StartContent> for OutgoingContent {
fn from(content: StartContent) -> Self {
match content {
StartContent::Room(c) => {
OutgoingContent::Room(AnyMessageEventContent::KeyVerificationStart(c))
}
StartContent::ToDevice(c) => {
OutgoingContent::ToDevice(AnyToDeviceEventContent::KeyVerificationStart(c))
}
}
}
}

View File

@ -38,7 +38,7 @@ use crate::{
ReadOnlyAccount, ToDeviceRequest,
};
use super::sas_state::FlowId;
use super::{event_enums::StartContent, sas_state::FlowId};
#[derive(Clone, Debug)]
pub struct SasIds {
@ -57,15 +57,12 @@ pub struct SasIds {
///
/// * `content` - The `m.key.verification.start` event content that started the
/// interactive verification process.
pub fn calculate_commitment(public_key: &str, content: &StartToDeviceEventContent) -> String {
let json_content: CanonicalJsonValue = serde_json::to_value(content)
.expect("Can't serialize content")
.try_into()
.expect("Can't canonicalize content");
pub fn calculate_commitment(public_key: &str, content: impl Into<StartContent>) -> String {
let content = content.into().to_canonical_json();
encode(
Sha256::new()
.chain(&format!("{}{}", public_key, json_content))
.chain(&format!("{}{}", public_key, content))
.finalize(),
)
}

View File

@ -17,12 +17,15 @@ use std::time::Instant;
use std::sync::Arc;
use matrix_sdk_common::events::{
key::verification::{
accept::AcceptToDeviceEventContent, cancel::CancelCode, mac::MacToDeviceEventContent,
start::StartToDeviceEventContent,
use matrix_sdk_common::{
events::{
key::verification::{
accept::AcceptToDeviceEventContent, cancel::CancelCode, mac::MacToDeviceEventContent,
start::StartToDeviceEventContent,
},
AnyToDeviceEvent, AnyToDeviceEventContent, ToDeviceEvent,
},
AnyToDeviceEvent, AnyToDeviceEventContent, ToDeviceEvent,
identifiers::EventId,
};
use crate::{
@ -30,9 +33,12 @@ use crate::{
ReadOnlyAccount,
};
use super::sas_state::{
Accepted, Canceled, Confirmed, Created, Done, FlowId, KeyReceived, MacReceived, SasState,
Started,
use super::{
event_enums::OutgoingContent,
sas_state::{
Accepted, Canceled, Confirmed, Created, Done, FlowId, KeyReceived, MacReceived, SasState,
Started,
},
};
#[derive(Clone, Debug)]
@ -52,12 +58,23 @@ impl InnerSas {
account: ReadOnlyAccount,
other_device: ReadOnlyDevice,
other_identity: Option<UserIdentities>,
) -> (InnerSas, StartToDeviceEventContent) {
) -> (InnerSas, OutgoingContent) {
let sas = SasState::<Created>::new(account, other_device, other_identity);
let content = sas.as_content();
(InnerSas::Created(sas), content)
}
pub fn start_in_room(
event_id: EventId,
account: ReadOnlyAccount,
other_device: ReadOnlyDevice,
other_identity: Option<UserIdentities>,
) -> (InnerSas, OutgoingContent) {
let sas = SasState::<Created>::new_in_room(event_id, account, other_device, other_identity);
let content = sas.as_content();
(InnerSas::Created(sas), content)
}
pub fn from_start_event(
account: ReadOnlyAccount,
other_device: ReadOnlyDevice,

View File

@ -12,6 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
mod event_enums;
mod helpers;
mod inner_sas;
mod sas_state;
@ -46,6 +47,8 @@ pub use helpers::content_to_request;
use inner_sas::InnerSas;
pub use sas_state::FlowId;
pub use event_enums::{OutgoingContent, StartContent};
#[derive(Debug)]
/// A result of a verification flow.
pub enum VerificationResult {
@ -106,6 +109,30 @@ impl Sas {
self.inner.lock().unwrap().set_creation_time(time)
}
fn start_helper(
inner_sas: InnerSas,
content: OutgoingContent,
account: ReadOnlyAccount,
private_identity: PrivateCrossSigningIdentity,
other_device: ReadOnlyDevice,
store: Arc<Box<dyn CryptoStore>>,
other_identity: Option<UserIdentities>,
) -> (Sas, OutgoingContent) {
let flow_id = inner_sas.verification_flow_id();
let sas = Sas {
inner: Arc::new(Mutex::new(inner_sas)),
account,
private_identity,
store,
other_device,
flow_id,
other_identity,
};
(sas, content)
}
/// Start a new SAS auth flow with the given device.
///
/// # Arguments
@ -122,25 +149,60 @@ impl Sas {
other_device: ReadOnlyDevice,
store: Arc<Box<dyn CryptoStore>>,
other_identity: Option<UserIdentities>,
) -> (Sas, StartToDeviceEventContent) {
) -> (Sas, OutgoingContent) {
let (inner, content) = InnerSas::start(
account.clone(),
other_device.clone(),
other_identity.clone(),
);
let flow_id = inner.verification_flow_id();
let sas = Sas {
inner: Arc::new(Mutex::new(inner)),
Self::start_helper(
inner,
content,
account,
private_identity,
store,
other_device,
flow_id,
store,
other_identity,
};
)
}
(sas, content)
/// Start a new SAS auth flow with the given device inside the given room.
///
/// # Arguments
///
/// * `account` - Our own account.
///
/// * `other_device` - The other device which we are going to verify.
///
/// Returns the new `Sas` object and a `StartEventContent` that needs to be
/// sent out through the server to the other device.
#[allow(dead_code)]
pub(crate) fn start_in_room(
flow_id: EventId,
room_id: RoomId,
account: ReadOnlyAccount,
private_identity: PrivateCrossSigningIdentity,
other_device: ReadOnlyDevice,
store: Arc<Box<dyn CryptoStore>>,
other_identity: Option<UserIdentities>,
) -> (Sas, OutgoingContent) {
let (inner, content) = InnerSas::start_in_room(
flow_id,
account.clone(),
other_device.clone(),
other_identity.clone(),
);
Self::start_helper(
inner,
content,
account,
private_identity,
other_device,
store,
other_identity,
)
}
/// Create a new Sas object from a m.key.verification.start request.

View File

@ -30,19 +30,25 @@ use matrix_sdk_common::{
cancel::{CancelCode, CancelToDeviceEventContent},
key::KeyToDeviceEventContent,
mac::MacToDeviceEventContent,
start::{MSasV1Content, MSasV1ContentInit, StartMethod, StartToDeviceEventContent},
HashAlgorithm, KeyAgreementProtocol, MessageAuthenticationCode,
start::{
MSasV1Content, MSasV1ContentInit, StartEventContent, StartMethod,
StartToDeviceEventContent,
},
HashAlgorithm, KeyAgreementProtocol, MessageAuthenticationCode, Relation,
ShortAuthenticationString, VerificationMethod,
},
AnyToDeviceEventContent, ToDeviceEvent,
AnyMessageEventContent, AnyToDeviceEventContent, ToDeviceEvent,
},
identifiers::{DeviceId, RoomId, UserId},
identifiers::{DeviceId, EventId, UserId},
uuid::Uuid,
};
use tracing::error;
use super::helpers::{
calculate_commitment, get_decimal, get_emoji, get_mac_content, receive_mac_event, SasIds,
use super::{
event_enums::{OutgoingContent, StartContent},
helpers::{
calculate_commitment, get_decimal, get_emoji, get_mac_content, receive_mac_event, SasIds,
},
};
use crate::{
@ -68,7 +74,7 @@ const MAX_EVENT_TIMEOUT: Duration = Duration::from_secs(60);
#[derive(Clone, Debug)]
pub enum FlowId {
ToDevice(String),
InRoom(RoomId),
InRoom(EventId),
}
impl FlowId {
@ -200,7 +206,7 @@ pub struct Started {
#[derive(Clone, Debug)]
pub struct Accepted {
accepted_protocols: Arc<AcceptedProtocols>,
start_content: Arc<StartToDeviceEventContent>,
start_content: Arc<StartContent>,
commitment: String,
}
@ -310,13 +316,45 @@ impl SasState<Created> {
/// * `account` - Our own account.
///
/// * `other_device` - The other device which we are going to verify.
///
/// * `other_identity` - The identity of the other user if one exists.
pub fn new(
account: ReadOnlyAccount,
other_device: ReadOnlyDevice,
other_identity: Option<UserIdentities>,
) -> SasState<Created> {
let verification_flow_id = Uuid::new_v4().to_string();
let flow_id = FlowId::ToDevice(Uuid::new_v4().to_string());
Self::new_helper(flow_id, account, other_device, other_identity)
}
/// Create a new SAS in-room verification flow.
///
/// # Arguments
///
/// * `event_id` - The event id of the `m.key.verification.request` event
/// that started the verification flow.
///
/// * `account` - Our own account.
///
/// * `other_device` - The other device which we are going to verify.
///
/// * `other_identity` - The identity of the other user if one exists.
pub fn new_in_room(
event_id: EventId,
account: ReadOnlyAccount,
other_device: ReadOnlyDevice,
other_identity: Option<UserIdentities>,
) -> SasState<Created> {
let flow_id = FlowId::InRoom(event_id);
Self::new_helper(flow_id, account, other_device, other_identity)
}
fn new_helper(
flow_id: FlowId,
account: ReadOnlyAccount,
other_device: ReadOnlyDevice,
other_identity: Option<UserIdentities>,
) -> SasState<Created> {
SasState {
inner: Arc::new(Mutex::new(OlmSas::new())),
ids: SasIds {
@ -324,7 +362,7 @@ impl SasState<Created> {
other_device,
other_identity,
},
verification_flow_id: FlowId::ToDevice(verification_flow_id).into(),
verification_flow_id: flow_id.into(),
creation_time: Arc::new(Instant::now()),
last_event_time: Arc::new(Instant::now()),
@ -340,18 +378,34 @@ impl SasState<Created> {
}
}
pub fn as_start_content(&self) -> StartContent {
match self.verification_flow_id.as_ref() {
FlowId::ToDevice(s) => StartContent::ToDevice(StartToDeviceEventContent {
transaction_id: self.verification_flow_id.to_string(),
from_device: self.device_id().into(),
method: StartMethod::MSasV1(
MSasV1Content::new(self.state.protocol_definitions.clone())
.expect("Invalid initial protocol definitions."),
),
}),
FlowId::InRoom(e) => StartContent::Room(StartEventContent {
from_device: self.device_id().into(),
method: StartMethod::MSasV1(
MSasV1Content::new(self.state.protocol_definitions.clone())
.expect("Invalid initial protocol definitions."),
),
relation: Relation {
event_id: e.clone(),
},
}),
}
}
/// Get the content for the start event.
///
/// The content needs to be sent to the other device.
pub fn as_content(&self) -> StartToDeviceEventContent {
StartToDeviceEventContent {
transaction_id: self.verification_flow_id.to_string(),
from_device: self.device_id().into(),
method: StartMethod::MSasV1(
MSasV1Content::new(self.state.protocol_definitions.clone())
.expect("Invalid initial protocol definitions."),
),
}
pub fn as_content(&self) -> OutgoingContent {
self.as_start_content().into()
}
/// Receive a m.key.verification.accept event, changing the state into
@ -372,7 +426,7 @@ impl SasState<Created> {
let accepted_protocols =
AcceptedProtocols::try_from(content.clone()).map_err(|c| self.clone().cancel(c))?;
let start_content = self.as_content().into();
let start_content = self.as_start_content().into();
Ok(SasState {
inner: self.inner,
@ -416,7 +470,7 @@ impl SasState<Started> {
let sas = OlmSas::new();
let pubkey = sas.public_key();
let commitment = calculate_commitment(&pubkey, &event.content);
let commitment = calculate_commitment(&pubkey, event.content.clone());
error!(
"Calculated commitment for pubkey {} and content {:?} {}",
@ -565,7 +619,10 @@ impl SasState<Accepted> {
self.check_event(&event.sender, &event.content.transaction_id)
.map_err(|c| self.clone().cancel(c))?;
let commitment = calculate_commitment(&event.content.key, &self.state.start_content);
let commitment = calculate_commitment(
&event.content.key,
self.state.start_content.as_ref().clone(),
);
if self.state.commitment != commitment {
Err(self.cancel(CancelCode::InvalidMessage))