diff --git a/matrix_sdk/examples/cross_signing_bootstrap.rs b/matrix_sdk/examples/cross_signing_bootstrap.rs index 54def1cb..b6a4e69f 100644 --- a/matrix_sdk/examples/cross_signing_bootstrap.rs +++ b/matrix_sdk/examples/cross_signing_bootstrap.rs @@ -9,10 +9,8 @@ use serde_json::json; use url::Url; use matrix_sdk::{ - self, - api::r0::uiaa::AuthData, - identifiers::{user_id, UserId}, - Client, ClientConfig, LoopCtrl, SyncSettings, + self, api::r0::uiaa::AuthData, identifiers::UserId, Client, ClientConfig, LoopCtrl, + SyncSettings, }; fn auth_data<'a>(user: &UserId, password: &str, session: Option<&'a str>) -> AuthData<'a> { @@ -35,7 +33,7 @@ fn auth_data<'a>(user: &UserId, password: &str, session: Option<&'a str>) -> Aut } } -async fn bootstrap(client: Client) { +async fn bootstrap(client: Client, user_id: UserId, password: String) { println!("Bootstrapping a new cross signing identity, press enter to continue."); let mut input = String::new(); @@ -46,11 +44,7 @@ async fn bootstrap(client: Client) { if let Err(e) = client.bootstrap_cross_signing(None).await { if let Some(response) = e.uiaa_response() { - let auth_data = auth_data( - &user_id!("@example:localhost"), - "wordpass", - response.session.as_deref(), - ); + let auth_data = auth_data(&user_id, &password, response.session.as_deref()); client .bootstrap_cross_signing(Some(auth_data)) .await @@ -73,10 +67,11 @@ async fn login( let homeserver_url = Url::parse(&homeserver_url).expect("Couldn't parse the homeserver URL"); let client = Client::new_with_config(homeserver_url, client_config).unwrap(); - client + let response = client .login(username, password, None, Some("rust-sdk")) .await?; + let user_id = &response.user_id; let client_ref = &client; let asked = AtomicBool::new(false); let asked_ref = &asked; @@ -85,10 +80,16 @@ async fn login( .sync_with_callback(SyncSettings::new(), |_| async move { let asked = asked_ref; let client = &client_ref; + let user_id = &user_id; + let password = &password; // Wait for sync to be done then ask the user to bootstrap. if !asked.load(Ordering::SeqCst) { - tokio::spawn(bootstrap((*client).clone())); + tokio::spawn(bootstrap( + (*client).clone(), + (*user_id).clone(), + password.to_string(), + )); } asked.store(true, Ordering::SeqCst); diff --git a/matrix_sdk/src/client.rs b/matrix_sdk/src/client.rs index 4827fc05..b00bd39e 100644 --- a/matrix_sdk/src/client.rs +++ b/matrix_sdk/src/client.rs @@ -1451,10 +1451,10 @@ impl Client { sync_settings: SyncSettings<'_>, ) -> Result { let request = assign!(sync_events::Request::new(), { - filter: sync_settings.filter, + filter: sync_settings.filter.as_ref(), since: sync_settings.token.as_deref(), full_state: sync_settings.full_state, - set_presence: PresenceState::Online, + set_presence: &PresenceState::Online, timeout: sync_settings.timeout, }); @@ -1551,7 +1551,7 @@ impl Client { C: Future, { let mut sync_settings = sync_settings; - let filter = sync_settings.filter; + let filter = sync_settings.filter.clone(); let mut last_sync_time: Option = None; if sync_settings.token.is_none() { @@ -1600,6 +1600,15 @@ impl Client { .unwrap(); } } + OutgoingRequests::SignatureUpload(request) => { + // TODO remove this unwrap. + if let Ok(resp) = self.send(request.clone()).await { + self.base_client + .mark_request_as_sent(&r.request_id(), &resp) + .await + .unwrap(); + } + } } } } @@ -1627,7 +1636,7 @@ impl Client { .expect("No sync token found after initial sync"), ); if let Some(f) = filter.as_ref() { - sync_settings = sync_settings.filter(*f); + sync_settings = sync_settings.filter(f.clone()); } } } @@ -1807,7 +1816,58 @@ impl Client { })) } - /// TODO + /// Create and upload a new cross signing identity. + /// + /// # Arguments + /// + /// * `auth_data` - This request requires user interactive auth, the first + /// request needs to set this to `None` and will always fail with an + /// `UiaaResponse`. The response will contain information for the + /// interactive auth and the same request needs to be made but this time + /// with some `auth_data` provided. + /// + /// # Examples + /// ```no_run + /// # use std::{convert::TryFrom, collections::BTreeMap}; + /// # use matrix_sdk::{Client, identifiers::UserId}; + /// # use matrix_sdk::api::r0::uiaa::AuthData; + /// # use url::Url; + /// # use futures::executor::block_on; + /// # use serde_json::json; + /// # let user_id = UserId::try_from("@alice:example.org").unwrap(); + /// # let homeserver = Url::parse("http://example.com").unwrap(); + /// # let client = Client::new(homeserver).unwrap(); + /// # block_on(async { + /// + /// fn auth_data<'a>(user: &UserId, password: &str, session: Option<&'a str>) -> AuthData<'a> { + /// let mut auth_parameters = BTreeMap::new(); + /// let identifier = json!({ + /// "type": "m.id.user", + /// "user": user, + /// }); + /// auth_parameters.insert("identifier".to_owned(), identifier); + /// auth_parameters.insert("password".to_owned(), password.to_owned().into()); + /// // This is needed because of https://github.com/matrix-org/synapse/issues/5665 + /// auth_parameters.insert("user".to_owned(), user.as_str().into()); + /// AuthData::DirectRequest { + /// kind: "m.login.password", + /// auth_parameters, + /// session, + /// } + /// } + /// + /// if let Err(e) = client.bootstrap_cross_signing(None).await { + /// if let Some(response) = e.uiaa_response() { + /// let auth_data = auth_data(&user_id, "wordpass", response.session.as_deref()); + /// client + /// .bootstrap_cross_signing(Some(auth_data)) + /// .await + /// .expect("Couldn't bootstrap cross signing") + /// } else { + /// panic!("Error durign cross signing bootstrap {:#?}", e); + /// } + /// } + /// # }) #[cfg(feature = "encryption")] #[cfg_attr(feature = "docs", doc(cfg(encryption)))] pub async fn bootstrap_cross_signing(&self, auth_data: Option>) -> Result<()> { @@ -1819,8 +1879,6 @@ impl Client { let (request, signature_request) = olm.bootstrap_cross_signing(false).await?; - println!("HELLOOO MAKING REQUEST {:#?}", request); - let request = UploadSigningKeysRequest { auth: auth_data, master_key: request.master_key, diff --git a/matrix_sdk/src/sas.rs b/matrix_sdk/src/sas.rs index 4f885f70..3db1df47 100644 --- a/matrix_sdk/src/sas.rs +++ b/matrix_sdk/src/sas.rs @@ -38,13 +38,19 @@ impl Sas { /// Confirm that the short auth strings match on both sides. pub async fn confirm(&self) -> Result<()> { - if let Some(req) = self.inner.confirm().await? { + let (to_device, signature) = self.inner.confirm().await?; + + if let Some(req) = to_device { let txn_id_string = req.txn_id_string(); let request = ToDeviceRequest::new(req.event_type, &txn_id_string, req.messages); self.http_client.send(request).await?; } + if let Some(s) = signature { + self.http_client.send(s).await?; + } + Ok(()) } diff --git a/matrix_sdk_base/src/client.rs b/matrix_sdk_base/src/client.rs index 738981eb..e84edfb1 100644 --- a/matrix_sdk_base/src/client.rs +++ b/matrix_sdk_base/src/client.rs @@ -28,8 +28,8 @@ use matrix_sdk_common::locks::Mutex; use matrix_sdk_common::{ api::r0 as api, events::{ - room::member::MemberEventContent, AnyStrippedStateEvent, AnySyncRoomEvent, - AnySyncStateEvent, SyncStateEvent, AnySyncMessageEvent + room::member::MemberEventContent, AnyStrippedStateEvent, AnySyncMessageEvent, + AnySyncRoomEvent, AnySyncStateEvent, SyncStateEvent, }, identifiers::{RoomId, UserId}, locks::RwLock, @@ -51,11 +51,10 @@ use matrix_sdk_crypto::{ use tracing::info; use zeroize::Zeroizing; -use crate::store::RoomType; use crate::{ error::Result, session::Session, - store::{Room, StateChanges, Store}, + store::{Room, RoomType, StateChanges, Store}, }; pub type Token = String; @@ -104,7 +103,9 @@ pub fn hoist_and_deserialize_state_event( Ok(ev) } -fn hoist_room_event_prev_content(event: &Raw) -> StdResult { +fn hoist_room_event_prev_content( + event: &Raw, +) -> StdResult { let prev_content = serde_json::from_str::(event.json().get()) .map(|more_unsigned| more_unsigned.unsigned) .map(|additional| additional.prev_content)?; diff --git a/matrix_sdk_base/src/store.rs b/matrix_sdk_base/src/store.rs index 3455eb0f..745ff8ae 100644 --- a/matrix_sdk_base/src/store.rs +++ b/matrix_sdk_base/src/store.rs @@ -6,20 +6,17 @@ use std::{ }; use futures::executor::block_on; -use matrix_sdk_common::Raw; -use matrix_sdk_common::events::room::canonical_alias::CanonicalAliasEventContent; -use matrix_sdk_common::events::room::name::NameEventContent; -use matrix_sdk_common::identifiers::RoomAliasId; use matrix_sdk_common::{ api::r0::sync::sync_events::RoomSummary as RumaSummary, events::{ room::{ - create::CreateEventContent, encryption::EncryptionEventContent, - member::MemberEventContent, + canonical_alias::CanonicalAliasEventContent, create::CreateEventContent, + encryption::EncryptionEventContent, member::MemberEventContent, name::NameEventContent, }, AnySyncStateEvent, EventContent, SyncStateEvent, }, - identifiers::{RoomId, UserId}, + identifiers::{RoomAliasId, RoomId, UserId}, + Raw, }; use serde::{Deserialize, Serialize}; @@ -36,8 +33,7 @@ pub struct Store { room_summaries: Tree, } -use crate::Session; -use crate::client::hoist_and_deserialize_state_event; +use crate::{client::hoist_and_deserialize_state_event, Session}; #[derive(Debug, Default)] pub struct StateChanges { @@ -81,7 +77,8 @@ impl StateChanges { } pub fn add_room(&mut self, room: InnerSummary) { - self.room_summaries.insert(room.room_id.as_ref().to_owned(), room); + self.room_summaries + .insert(room.room_id.as_ref().to_owned(), room); } pub fn add_state_event(&mut self, room_id: &RoomId, event: AnySyncStateEvent) { @@ -169,7 +166,11 @@ impl Room { todo!(); } - pub fn handle_state_event(&self, summary: &mut InnerSummary, event: &AnySyncStateEvent) -> bool { + pub fn handle_state_event( + &self, + summary: &mut InnerSummary, + event: &AnySyncStateEvent, + ) -> bool { match event { AnySyncStateEvent::RoomEncryption(encryption) => { info!("MARKING ROOM {} AS ENCRYPTED", self.room_id); diff --git a/matrix_sdk_crypto/src/identities/device.rs b/matrix_sdk_crypto/src/identities/device.rs index 0a75ad29..568d80f9 100644 --- a/matrix_sdk_crypto/src/identities/device.rs +++ b/matrix_sdk_crypto/src/identities/device.rs @@ -40,7 +40,7 @@ use serde_json::{json, Value}; use tracing::warn; use crate::{ - olm::{InboundGroupSession, Session}, + olm::{InboundGroupSession, PrivateCrossSigningIdentity, Session}, store::{Changes, DeviceChanges}, }; #[cfg(test)] @@ -62,7 +62,7 @@ pub struct ReadOnlyDevice { device_id: Arc>, algorithms: Arc>, keys: Arc>, - signatures: Arc>>, + pub(crate) signatures: Arc>>, display_name: Arc>, deleted: Arc, trust_state: Arc>, @@ -72,6 +72,7 @@ pub struct ReadOnlyDevice { /// A device represents a E2EE capable client of an user. pub struct Device { pub(crate) inner: ReadOnlyDevice, + pub(crate) private_identity: Arc>, pub(crate) verification_machine: VerificationMachine, pub(crate) own_identity: Option, pub(crate) device_owner_identity: Option, @@ -179,6 +180,7 @@ impl Device { #[derive(Debug)] pub struct UserDevices { pub(crate) inner: HashMap, + pub(crate) private_identity: Arc>, pub(crate) verification_machine: VerificationMachine, pub(crate) own_identity: Option, pub(crate) device_owner_identity: Option, @@ -189,6 +191,7 @@ impl UserDevices { pub fn get(&self, device_id: &DeviceId) -> Option { self.inner.get(device_id).map(|d| Device { inner: d.clone(), + private_identity: self.private_identity.clone(), verification_machine: self.verification_machine.clone(), own_identity: self.own_identity.clone(), device_owner_identity: self.device_owner_identity.clone(), @@ -204,6 +207,7 @@ impl UserDevices { pub fn devices(&self) -> impl Iterator + '_ { self.inner.values().map(move |d| Device { inner: d.clone(), + private_identity: self.private_identity.clone(), verification_machine: self.verification_machine.clone(), own_identity: self.own_identity.clone(), device_owner_identity: self.device_owner_identity.clone(), @@ -438,6 +442,17 @@ impl ReadOnlyDevice { ) } + pub(crate) fn as_device_keys(&self) -> DeviceKeys { + DeviceKeys { + user_id: self.user_id().clone(), + device_id: self.device_id().into(), + keys: self.keys().clone(), + algorithms: self.algorithms().to_vec(), + signatures: self.signatures().to_owned(), + unsigned: Default::default(), + } + } + pub(crate) fn as_signature_message(&self) -> Value { json!({ "user_id": &*self.user_id, diff --git a/matrix_sdk_crypto/src/identities/manager.rs b/matrix_sdk_crypto/src/identities/manager.rs index 993dd5a4..02a440a0 100644 --- a/matrix_sdk_crypto/src/identities/manager.rs +++ b/matrix_sdk_crypto/src/identities/manager.rs @@ -415,9 +415,10 @@ pub(crate) mod test { let user_id = Arc::new(user_id()); let account = ReadOnlyAccount::new(&user_id, &device_id()); let store: Arc> = Arc::new(Box::new(MemoryStore::new())); - let verification = VerificationMachine::new(account.clone(), identity, store); + let verification = VerificationMachine::new(account.clone(), identity.clone(), store); let store = Store::new( user_id.clone(), + identity, Arc::new(Box::new(MemoryStore::new())), verification, ); diff --git a/matrix_sdk_crypto/src/identities/user.rs b/matrix_sdk_crypto/src/identities/user.rs index 70026de0..a53c9570 100644 --- a/matrix_sdk_crypto/src/identities/user.rs +++ b/matrix_sdk_crypto/src/identities/user.rs @@ -29,6 +29,8 @@ use matrix_sdk_common::{ identifiers::{DeviceKeyId, UserId}, }; +#[cfg(test)] +use crate::olm::PrivateCrossSigningIdentity; use crate::{error::SignatureError, olm::Utility, ReadOnlyDevice}; /// Wrapper for a cross signing key marking it as the master key. @@ -278,7 +280,10 @@ impl UserSigningPubkey { /// /// Returns an empty result if the signature check succeeded, otherwise a /// SignatureError indicating why the check failed. - fn verify_master_key(&self, master_key: &MasterPubkey) -> Result<(), SignatureError> { + pub(crate) fn verify_master_key( + &self, + master_key: &MasterPubkey, + ) -> Result<(), SignatureError> { let (key_id, key) = self .0 .keys @@ -326,7 +331,7 @@ impl SelfSigningPubkey { /// /// Returns an empty result if the signature check succeeded, otherwise a /// SignatureError indicating why the check failed. - fn verify_device(&self, device: &ReadOnlyDevice) -> Result<(), SignatureError> { + pub(crate) fn verify_device(&self, device: &ReadOnlyDevice) -> Result<(), SignatureError> { let (key_id, key) = self .0 .keys @@ -443,7 +448,7 @@ impl PartialEq for UserIdentities { #[derive(Debug, Clone, Deserialize, Serialize)] pub struct UserIdentity { user_id: Arc, - master_key: MasterPubkey, + pub(crate) master_key: MasterPubkey, self_signing_key: SelfSigningPubkey, } @@ -471,6 +476,32 @@ impl UserIdentity { }) } + #[cfg(test)] + pub async fn from_private(identity: &PrivateCrossSigningIdentity) -> Self { + let master_key = identity + .master_key + .lock() + .await + .as_ref() + .unwrap() + .public_key + .clone(); + let self_signing_key = identity + .self_signing_key + .lock() + .await + .as_ref() + .unwrap() + .public_key + .clone(); + + Self { + user_id: Arc::new(identity.user_id().clone()), + master_key, + self_signing_key, + } + } + /// Get the user id of this identity. pub fn user_id(&self) -> &UserId { &self.user_id @@ -692,6 +723,7 @@ pub(crate) mod test { use matrix_sdk_common::{ api::r0::keys::get_keys::Response as KeyQueryResponse, identifiers::user_id, locks::Mutex, }; + use matrix_sdk_test::async_test; use super::{OwnUserIdentity, UserIdentities, UserIdentity}; @@ -757,13 +789,14 @@ pub(crate) mod test { ))); let verification_machine = VerificationMachine::new( ReadOnlyAccount::new(second.user_id(), second.device_id()), - private_identity, + private_identity.clone(), Arc::new(Box::new(MemoryStore::new())), ); let first = Device { inner: first, verification_machine: verification_machine.clone(), + private_identity: private_identity.clone(), own_identity: Some(identity.clone()), device_owner_identity: Some(UserIdentities::Own(identity.clone())), }; @@ -771,6 +804,7 @@ pub(crate) mod test { let second = Device { inner: second, verification_machine, + private_identity, own_identity: Some(identity.clone()), device_owner_identity: Some(UserIdentities::Own(identity.clone())), }; @@ -785,4 +819,39 @@ pub(crate) mod test { assert!(second.trust_state()); assert!(!first.trust_state()); } + + #[async_test] + async fn own_device_with_private_identity() { + let response = own_key_query(); + let (_, device) = device(&response); + + let account = ReadOnlyAccount::new(device.user_id(), device.device_id()); + let (identity, _, _) = PrivateCrossSigningIdentity::new_with_account(&account).await; + + let id = Arc::new(Mutex::new(identity.clone())); + + let verification_machine = VerificationMachine::new( + ReadOnlyAccount::new(device.user_id(), device.device_id()), + id.clone(), + Arc::new(Box::new(MemoryStore::new())), + ); + + let public_identity = identity.as_public_identity().await.unwrap(); + + let mut device = Device { + inner: device, + verification_machine: verification_machine.clone(), + private_identity: id.clone(), + own_identity: Some(public_identity.clone()), + device_owner_identity: Some(public_identity.clone().into()), + }; + + assert!(!device.trust_state()); + + let mut device_keys = device.as_device_keys(); + + identity.sign_device_keys(&mut device_keys).await.unwrap(); + device.inner.signatures = Arc::new(device_keys.signatures); + assert!(device.trust_state()); + } } diff --git a/matrix_sdk_crypto/src/key_request.rs b/matrix_sdk_crypto/src/key_request.rs index 7baf72d5..6c4e0cff 100644 --- a/matrix_sdk_crypto/src/key_request.rs +++ b/matrix_sdk_crypto/src/key_request.rs @@ -722,8 +722,8 @@ mod test { let account = ReadOnlyAccount::new(&user_id, &alice_device_id()); let store: Arc> = Arc::new(Box::new(MemoryStore::new())); let identity = Arc::new(Mutex::new(PrivateCrossSigningIdentity::empty(bob_id()))); - let verification = VerificationMachine::new(account, identity, store.clone()); - let store = Store::new(user_id.clone(), store, verification); + let verification = VerificationMachine::new(account, identity.clone(), store.clone()); + let store = Store::new(user_id.clone(), identity, store, verification); KeyRequestMachine::new( user_id, @@ -740,8 +740,8 @@ mod test { let device = ReadOnlyDevice::from_account(&account).await; let store: Arc> = Arc::new(Box::new(MemoryStore::new())); let identity = Arc::new(Mutex::new(PrivateCrossSigningIdentity::empty(alice_id()))); - let verification = VerificationMachine::new(account, identity, store.clone()); - let store = Store::new(user_id.clone(), store, verification); + let verification = VerificationMachine::new(account, identity.clone(), store.clone()); + let store = Store::new(user_id.clone(), identity, store, verification); store.save_devices(&[device]).await.unwrap(); KeyRequestMachine::new( diff --git a/matrix_sdk_crypto/src/machine.rs b/matrix_sdk_crypto/src/machine.rs index b3d5589a..16821b18 100644 --- a/matrix_sdk_crypto/src/machine.rs +++ b/matrix_sdk_crypto/src/machine.rs @@ -147,7 +147,12 @@ impl OlmMachine { let store = Arc::new(store); let verification_machine = VerificationMachine::new(account.clone(), user_identity.clone(), store.clone()); - let store = Store::new(user_id.clone(), store, verification_machine.clone()); + let store = Store::new( + user_id.clone(), + user_identity.clone(), + store, + verification_machine.clone(), + ); let device_id: Arc = Arc::new(device_id); let outbound_group_sessions = Arc::new(DashMap::new()); let users_for_key_claim = Arc::new(DashMap::new()); @@ -351,6 +356,9 @@ impl OlmMachine { IncomingResponse::SigningKeysUpload(_) => { self.receive_cross_signing_upload_response().await?; } + IncomingResponse::SignatureUpload(_) => { + self.verification_machine.mark_request_as_sent(request_id); + } }; Ok(()) @@ -380,20 +388,31 @@ impl OlmMachine { if identity.is_empty().await || reset { info!("Creating new cross signing identity"); - let (id, signature_request) = self.account.bootstrap_cross_signing().await; - let request = id.as_upload_request().await; + let (id, request, signature_request) = self.account.bootstrap_cross_signing().await; *identity = id; + let public = identity.as_public_identity().await.expect( + "Couldn't create a public version of the identity from a new private identity", + ); + + let changes = Changes { + identities: IdentityChanges { + new: vec![public.into()], + ..Default::default() + }, + ..Default::default() + }; + + self.store.save_changes(changes).await?; self.store.save_identity(identity.clone()).await?; Ok((request, signature_request)) } else { info!("Trying to upload the existing cross signing identity"); let request = identity.as_upload_request().await; - let device_keys = self.account.unsigned_device_keys(); // TODO remove this expect. let signature_request = identity - .sign_device(device_keys) + .sign_account(&self.account) .await .expect("Can't sign device keys"); Ok((request, signature_request)) @@ -1787,6 +1806,7 @@ pub(crate) mod test { .confirm() .await .unwrap() + .0 .map(|r| request_to_event(bob.user_id(), &r)) .unwrap(); alice.handle_verification_event(&mut event).await; @@ -1798,6 +1818,7 @@ pub(crate) mod test { .confirm() .await .unwrap() + .0 .map(|r| request_to_event(alice.user_id(), &r)) .unwrap(); diff --git a/matrix_sdk_crypto/src/olm/account.rs b/matrix_sdk_crypto/src/olm/account.rs index 49ce924f..150bef6c 100644 --- a/matrix_sdk_crypto/src/olm/account.rs +++ b/matrix_sdk_crypto/src/olm/account.rs @@ -54,6 +54,7 @@ use olm_rs::{ use crate::{ error::{EventError, OlmResult, SessionCreationError, SignatureError}, identities::ReadOnlyDevice, + requests::UploadSigningKeysRequest, store::Store, OlmError, }; @@ -668,7 +669,11 @@ impl ReadOnlyAccount { pub(crate) async fn bootstrap_cross_signing( &self, - ) -> (PrivateCrossSigningIdentity, SignatureUploadRequest) { + ) -> ( + PrivateCrossSigningIdentity, + UploadSigningKeysRequest, + SignatureUploadRequest, + ) { PrivateCrossSigningIdentity::new_with_account(self).await } diff --git a/matrix_sdk_crypto/src/olm/signing.rs b/matrix_sdk_crypto/src/olm/signing.rs deleted file mode 100644 index d4332501..00000000 --- a/matrix_sdk_crypto/src/olm/signing.rs +++ /dev/null @@ -1,772 +0,0 @@ -// 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, missing_docs)] - -use aes_gcm::{ - aead::{generic_array::GenericArray, Aead, NewAead}, - Aes256Gcm, -}; -use base64::{decode_config, encode_config, DecodeError, URL_SAFE_NO_PAD}; -use getrandom::getrandom; -use matrix_sdk_common::{ - encryption::DeviceKeys, - identifiers::{DeviceKeyAlgorithm, DeviceKeyId}, -}; -use serde::{Deserialize, Serialize}; -use serde_json::{Error as JsonError, Value}; -use std::{ - collections::BTreeMap, - sync::{ - atomic::{AtomicBool, Ordering}, - Arc, - }, -}; -use thiserror::Error; -use zeroize::Zeroizing; - -use olm_rs::{errors::OlmUtilityError, pk::OlmPkSigning, utility::OlmUtility}; - -use matrix_sdk_common::{ - api::r0::keys::{ - upload_signatures::Request as SignatureUploadRequest, CrossSigningKey, KeyUsage, - }, - identifiers::UserId, - locks::Mutex, -}; - -use crate::{ - error::SignatureError, - identities::{MasterPubkey, SelfSigningPubkey, UserSigningPubkey}, - requests::UploadSigningKeysRequest, - UserIdentity, -}; - -use crate::ReadOnlyAccount; - -const NONCE_SIZE: usize = 12; - -fn encode>(input: T) -> String { - encode_config(input, URL_SAFE_NO_PAD) -} - -fn decode>(input: T) -> Result, DecodeError> { - decode_config(input, URL_SAFE_NO_PAD) -} - -/// Error type reporting failures in the Signign operations. -#[derive(Debug, Error)] -pub enum SigningError { - /// Error decoding the base64 encoded pickle data. - #[error(transparent)] - Decode(#[from] DecodeError), - - /// Error decrypting the pickled signing seed - #[error("Error decrypting the pickled signign seed")] - Decryption(String), - - /// Error deserializing the pickle data. - #[error(transparent)] - Json(#[from] JsonError), -} - -#[derive(Clone)] -pub struct Signing { - inner: Arc>, - seed: Arc>>, - public_key: PublicSigningKey, -} - -impl std::fmt::Debug for Signing { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("Signing") - .field("public_key", &self.public_key.as_str()) - .finish() - } -} - -impl PartialEq for Signing { - fn eq(&self, other: &Signing) -> bool { - self.seed == other.seed - } -} - -#[derive(Clone, PartialEq, Debug)] -struct MasterSigning { - inner: Signing, - public_key: MasterPubkey, -} - -#[derive(Debug, Clone, Deserialize, Serialize)] -struct PickledMasterSigning { - pickle: PickledSigning, - public_key: CrossSigningKey, -} - -#[derive(Debug, Clone, Deserialize, Serialize)] -struct PickledUserSigning { - pickle: PickledSigning, - public_key: CrossSigningKey, -} - -#[derive(Debug, Clone, Deserialize, Serialize)] -struct PickledSelfSigning { - pickle: PickledSigning, - public_key: CrossSigningKey, -} - -impl MasterSigning { - async fn pickle(&self, pickle_key: &[u8]) -> PickledMasterSigning { - let pickle = self.inner.pickle(pickle_key).await; - let public_key = self.public_key.clone().into(); - PickledMasterSigning { pickle, public_key } - } - - fn from_pickle(pickle: PickledMasterSigning, pickle_key: &[u8]) -> Result { - let inner = Signing::from_pickle(pickle.pickle, pickle_key)?; - - Ok(Self { - inner, - public_key: pickle.public_key.into(), - }) - } - - async fn sign_subkey<'a>(&self, subkey: &mut CrossSigningKey) { - // TODO create a borrowed version of a cross singing key. - let subkey_wihtout_signatures = CrossSigningKey { - user_id: subkey.user_id.clone(), - keys: subkey.keys.clone(), - usage: subkey.usage.clone(), - signatures: BTreeMap::new(), - }; - - let message = cjson::to_string(&subkey_wihtout_signatures) - .expect("Can't serialize cross signing subkey"); - let signature = self.inner.sign(&message).await; - - subkey - .signatures - .entry(self.public_key.user_id().to_owned()) - .or_insert_with(BTreeMap::new) - .insert( - format!("ed25519:{}", self.inner.public_key().as_str()), - signature.0, - ); - } -} - -impl UserSigning { - async fn pickle(&self, pickle_key: &[u8]) -> PickledUserSigning { - let pickle = self.inner.pickle(pickle_key).await; - let public_key = self.public_key.clone().into(); - PickledUserSigning { pickle, public_key } - } - - async fn sign_user(&self, _: &UserIdentity) -> BTreeMap> { - todo!(); - } - - fn from_pickle(pickle: PickledUserSigning, pickle_key: &[u8]) -> Result { - let inner = Signing::from_pickle(pickle.pickle, pickle_key)?; - - Ok(Self { - inner, - public_key: pickle.public_key.into(), - }) - } -} - -impl SelfSigning { - async fn pickle(&self, pickle_key: &[u8]) -> PickledSelfSigning { - let pickle = self.inner.pickle(pickle_key).await; - let public_key = self.public_key.clone().into(); - PickledSelfSigning { pickle, public_key } - } - - async fn sign_device_raw(&self, value: Value) -> Result { - self.inner.sign_json(value).await - } - - async fn sign_device(&self, device_keys: &mut DeviceKeys) -> Result<(), SignatureError> { - let json_device = serde_json::to_value(&device_keys)?; - let signature = self.sign_device_raw(json_device).await?; - - device_keys - .signatures - .entry(self.public_key.user_id().to_owned()) - .or_insert_with(BTreeMap::new) - .insert( - DeviceKeyId::from_parts( - DeviceKeyAlgorithm::Ed25519, - self.inner.public_key.as_str().into(), - ), - signature.0, - ); - - Ok(()) - } - - fn from_pickle(pickle: PickledSelfSigning, pickle_key: &[u8]) -> Result { - let inner = Signing::from_pickle(pickle.pickle, pickle_key)?; - - Ok(Self { - inner, - public_key: pickle.public_key.into(), - }) - } -} - -#[derive(Clone, PartialEq, Debug)] -struct SelfSigning { - inner: Signing, - public_key: SelfSigningPubkey, -} - -#[derive(Clone, PartialEq, Debug)] -struct UserSigning { - inner: Signing, - public_key: UserSigningPubkey, -} - -#[derive(Clone, Debug)] -pub struct PrivateCrossSigningIdentity { - user_id: Arc, - shared: Arc, - master_key: Arc>>, - user_signing_key: Arc>>, - self_signing_key: Arc>>, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct PickledCrossSigningIdentity { - pub user_id: UserId, - pub shared: bool, - pub pickle: String, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -struct PickledSignings { - master_key: Option, - user_signing_key: Option, - self_signing_key: Option, -} - -#[derive(Debug, Clone)] -pub struct Signature(String); - -#[derive(Debug, Clone, Serialize, Deserialize)] -struct PickledSigning(String); - -#[derive(Debug, Clone, Serialize, Deserialize)] -struct InnerPickle { - version: u8, - nonce: String, - ciphertext: String, -} - -#[derive(Debug, Clone, PartialEq, Eq)] -struct PublicSigningKey(Arc); - -impl Signature { - fn as_str(&self) -> &str { - &self.0 - } -} - -impl PickledSigning { - fn as_str(&self) -> &str { - &self.0 - } -} - -impl PublicSigningKey { - fn as_str(&self) -> &str { - &self.0 - } - - #[allow(clippy::inherent_to_string)] - fn to_string(&self) -> String { - self.0.to_string() - } -} - -impl Signing { - fn new() -> Self { - let seed = OlmPkSigning::generate_seed(); - Self::from_seed(seed) - } - - fn from_seed(seed: Vec) -> Self { - let inner = OlmPkSigning::new(seed.clone()).expect("Unable to create pk signing object"); - let public_key = PublicSigningKey(Arc::new(inner.public_key().to_owned())); - - Signing { - inner: Arc::new(Mutex::new(inner)), - seed: Arc::new(Zeroizing::from(seed)), - public_key, - } - } - - fn from_pickle(pickle: PickledSigning, pickle_key: &[u8]) -> Result { - let pickled: InnerPickle = serde_json::from_str(pickle.as_str())?; - - let key = GenericArray::from_slice(pickle_key); - let cipher = Aes256Gcm::new(key); - - let nonce = decode(pickled.nonce)?; - let nonce = GenericArray::from_slice(&nonce); - let ciphertext = &decode(pickled.ciphertext)?; - - let seed = cipher - .decrypt(&nonce, ciphertext.as_slice()) - .map_err(|e| SigningError::Decryption(e.to_string()))?; - - Ok(Self::from_seed(seed)) - } - - async fn pickle(&self, pickle_key: &[u8]) -> PickledSigning { - let key = GenericArray::from_slice(pickle_key); - let cipher = Aes256Gcm::new(key); - - let mut nonce = vec![0u8; NONCE_SIZE]; - getrandom(&mut nonce).expect("Can't generate nonce to pickle the signing object"); - let nonce = GenericArray::from_slice(nonce.as_slice()); - - let ciphertext = cipher - .encrypt(nonce, self.seed.as_slice()) - .expect("Can't encrypt signing pickle"); - - let ciphertext = encode(ciphertext); - - let pickle = InnerPickle { - version: 1, - nonce: encode(nonce.as_slice()), - ciphertext, - }; - - PickledSigning(serde_json::to_string(&pickle).expect("Can't encode pickled signing")) - } - - fn public_key(&self) -> &PublicSigningKey { - &self.public_key - } - - fn cross_signing_key(&self, user_id: UserId, usage: KeyUsage) -> CrossSigningKey { - let mut keys = BTreeMap::new(); - - keys.insert( - format!("ed25519:{}", self.public_key().as_str()), - self.public_key().to_string(), - ); - - CrossSigningKey { - user_id, - usage: vec![usage], - keys, - signatures: BTreeMap::new(), - } - } - - async fn verify(&self, message: &str, signature: &Signature) -> Result { - let utility = OlmUtility::new(); - utility.ed25519_verify(self.public_key.as_str(), message, signature.as_str()) - } - - async fn sign_json(&self, mut json: Value) -> Result { - let json_object = json.as_object_mut().ok_or(SignatureError::NotAnObject)?; - let _ = json_object.remove("signatures"); - let canonical_json = cjson::to_string(json_object)?; - Ok(self.sign(&canonical_json).await) - } - - async fn sign(&self, message: &str) -> Signature { - Signature(self.inner.lock().await.sign(message)) - } -} - -impl PrivateCrossSigningIdentity { - pub fn user_id(&self) -> &UserId { - &self.user_id - } - - pub async fn is_empty(&self) -> bool { - let has_master = self.master_key.lock().await.is_some(); - let has_user = self.user_signing_key.lock().await.is_some(); - let has_self = self.self_signing_key.lock().await.is_some(); - - !(has_master && has_user && has_self) - } - - pub(crate) fn empty(user_id: UserId) -> Self { - Self { - user_id: Arc::new(user_id), - shared: Arc::new(AtomicBool::new(false)), - master_key: Arc::new(Mutex::new(None)), - self_signing_key: Arc::new(Mutex::new(None)), - user_signing_key: Arc::new(Mutex::new(None)), - } - } - - pub(crate) async fn sign_device( - &self, - mut device_keys: DeviceKeys, - ) -> Result { - self.self_signing_key - .lock() - .await - .as_ref() - .ok_or(SignatureError::MissingSigningKey)? - .sign_device(&mut device_keys) - .await?; - - let mut signed_keys = BTreeMap::new(); - signed_keys - .entry((&*self.user_id).to_owned()) - .or_insert_with(BTreeMap::new) - .insert( - device_keys.device_id.to_string(), - serde_json::to_value(device_keys)?, - ); - - Ok(SignatureUploadRequest { signed_keys }) - } - - pub(crate) async fn new_with_account( - account: &ReadOnlyAccount, - ) -> (Self, SignatureUploadRequest) { - let master = Signing::new(); - - let mut public_key = - master.cross_signing_key(account.user_id().to_owned(), KeyUsage::Master); - let signature = account - .sign_json( - serde_json::to_value(&public_key) - .expect("Can't convert own public master key to json"), - ) - .await - .expect("Can't sign own public master key"); - public_key - .signatures - .entry(account.user_id().to_owned()) - .or_insert_with(BTreeMap::new) - .insert(format!("ed25519:{}", account.device_id()), signature); - - let master = MasterSigning { - inner: master, - public_key: public_key.into(), - }; - - let identity = Self::new_helper(account.user_id(), master).await; - let device_keys = account.unsigned_device_keys(); - let request = identity - .sign_device(device_keys) - .await - .expect("Can't sign own device with new cross signign keys"); - - (identity, request) - } - - async fn new_helper(user_id: &UserId, master: MasterSigning) -> Self { - let user = Signing::new(); - let mut public_key = user.cross_signing_key(user_id.to_owned(), KeyUsage::UserSigning); - master.sign_subkey(&mut public_key).await; - - let user = UserSigning { - inner: user, - public_key: public_key.into(), - }; - - let self_signing = Signing::new(); - let mut public_key = - self_signing.cross_signing_key(user_id.to_owned(), KeyUsage::SelfSigning); - master.sign_subkey(&mut public_key).await; - - let self_signing = SelfSigning { - inner: self_signing, - public_key: public_key.into(), - }; - - Self { - user_id: Arc::new(user_id.to_owned()), - shared: Arc::new(AtomicBool::new(false)), - master_key: Arc::new(Mutex::new(Some(master))), - self_signing_key: Arc::new(Mutex::new(Some(self_signing))), - user_signing_key: Arc::new(Mutex::new(Some(user))), - } - } - - pub(crate) async fn new(user_id: UserId) -> Self { - let master = Signing::new(); - - let public_key = master.cross_signing_key(user_id.clone(), KeyUsage::Master); - let master = MasterSigning { - inner: master, - public_key: public_key.into(), - }; - - let user = Signing::new(); - let mut public_key = user.cross_signing_key(user_id.clone(), KeyUsage::UserSigning); - master.sign_subkey(&mut public_key).await; - - let user = UserSigning { - inner: user, - public_key: public_key.into(), - }; - - let self_signing = Signing::new(); - let mut public_key = self_signing.cross_signing_key(user_id.clone(), KeyUsage::SelfSigning); - master.sign_subkey(&mut public_key).await; - - let self_signing = SelfSigning { - inner: self_signing, - public_key: public_key.into(), - }; - - Self { - user_id: Arc::new(user_id), - shared: Arc::new(AtomicBool::new(false)), - master_key: Arc::new(Mutex::new(Some(master))), - self_signing_key: Arc::new(Mutex::new(Some(self_signing))), - user_signing_key: Arc::new(Mutex::new(Some(user))), - } - } - - pub fn mark_as_shared(&self) { - self.shared.store(true, Ordering::SeqCst) - } - - pub fn shared(&self) -> bool { - self.shared.load(Ordering::SeqCst) - } - - pub async fn pickle( - &self, - pickle_key: &[u8], - ) -> Result { - let master_key = if let Some(m) = self.master_key.lock().await.as_ref() { - Some(m.pickle(pickle_key).await) - } else { - None - }; - - let self_signing_key = if let Some(m) = self.self_signing_key.lock().await.as_ref() { - Some(m.pickle(pickle_key).await) - } else { - None - }; - - let user_signing_key = if let Some(m) = self.user_signing_key.lock().await.as_ref() { - Some(m.pickle(pickle_key).await) - } else { - None - }; - - let pickle = PickledSignings { - master_key, - user_signing_key, - self_signing_key, - }; - - let pickle = serde_json::to_string(&pickle)?; - - Ok(PickledCrossSigningIdentity { - user_id: self.user_id.as_ref().to_owned(), - shared: self.shared(), - pickle, - }) - } - - /// Restore the private cross signing identity from a pickle. - /// - /// # Panic - /// - /// Panics if the pickle_key isn't 32 bytes long. - pub async fn from_pickle( - pickle: PickledCrossSigningIdentity, - pickle_key: &[u8], - ) -> Result { - let signings: PickledSignings = serde_json::from_str(&pickle.pickle)?; - - let master = if let Some(m) = signings.master_key { - Some(MasterSigning::from_pickle(m, pickle_key)?) - } else { - None - }; - - let self_signing = if let Some(s) = signings.self_signing_key { - Some(SelfSigning::from_pickle(s, pickle_key)?) - } else { - None - }; - - let user_signing = if let Some(u) = signings.user_signing_key { - Some(UserSigning::from_pickle(u, pickle_key)?) - } else { - None - }; - - Ok(Self { - user_id: Arc::new(pickle.user_id), - shared: Arc::new(AtomicBool::from(pickle.shared)), - master_key: Arc::new(Mutex::new(master)), - self_signing_key: Arc::new(Mutex::new(self_signing)), - user_signing_key: Arc::new(Mutex::new(user_signing)), - }) - } - - pub(crate) async fn as_upload_request(&self) -> UploadSigningKeysRequest { - let master_key = self - .master_key - .lock() - .await - .as_ref() - .cloned() - .map(|k| k.public_key.into()); - - let user_signing_key = self - .user_signing_key - .lock() - .await - .as_ref() - .cloned() - .map(|k| k.public_key.into()); - - let self_signing_key = self - .self_signing_key - .lock() - .await - .as_ref() - .cloned() - .map(|k| k.public_key.into()); - - UploadSigningKeysRequest { - master_key, - user_signing_key, - self_signing_key, - } - } -} - -#[cfg(test)] -mod test { - use crate::olm::ReadOnlyAccount; - - use super::{PrivateCrossSigningIdentity, Signing}; - - use matrix_sdk_common::identifiers::{user_id, UserId}; - use matrix_sdk_test::async_test; - - fn user_id() -> UserId { - user_id!("@example:localhost") - } - - fn pickle_key() -> &'static [u8] { - &[0u8; 32] - } - - #[test] - fn signing_creation() { - let signing = Signing::new(); - assert!(!signing.public_key().as_str().is_empty()); - } - - #[async_test] - async fn signature_verification() { - let signing = Signing::new(); - - let message = "Hello world"; - - let signature = signing.sign(message).await; - assert!(signing.verify(message, &signature).await.is_ok()); - } - - #[async_test] - async fn pickling_signing() { - let signing = Signing::new(); - let pickled = signing.pickle(pickle_key()).await; - - let unpickled = Signing::from_pickle(pickled, pickle_key()).unwrap(); - - assert_eq!(signing.public_key(), unpickled.public_key()); - } - - #[async_test] - async fn private_identity_creation() { - let identity = PrivateCrossSigningIdentity::new(user_id()).await; - - let master_key = identity.master_key.lock().await; - let master_key = master_key.as_ref().unwrap(); - - assert!(master_key - .public_key - .verify_subkey( - &identity - .self_signing_key - .lock() - .await - .as_ref() - .unwrap() - .public_key, - ) - .is_ok()); - - assert!(master_key - .public_key - .verify_subkey( - &identity - .user_signing_key - .lock() - .await - .as_ref() - .unwrap() - .public_key, - ) - .is_ok()); - } - - #[async_test] - async fn identity_pickling() { - let identity = PrivateCrossSigningIdentity::new(user_id()).await; - - let pickled = identity.pickle(pickle_key()).await.unwrap(); - - let unpickled = PrivateCrossSigningIdentity::from_pickle(pickled, pickle_key()) - .await - .unwrap(); - - assert_eq!(identity.user_id, unpickled.user_id); - assert_eq!( - &*identity.master_key.lock().await, - &*unpickled.master_key.lock().await - ); - assert_eq!( - &*identity.user_signing_key.lock().await, - &*unpickled.user_signing_key.lock().await - ); - assert_eq!( - &*identity.self_signing_key.lock().await, - &*unpickled.self_signing_key.lock().await - ); - } - - #[async_test] - async fn private_identity_signed_by_accound() { - let account = ReadOnlyAccount::new(&user_id(), "DEVICEID".into()); - let (identity, _) = PrivateCrossSigningIdentity::new_with_account(&account).await; - let master = identity.master_key.lock().await; - let master = master.as_ref().unwrap(); - - assert!(!master.public_key.signatures().is_empty()); - } -} diff --git a/matrix_sdk_crypto/src/olm/signing/mod.rs b/matrix_sdk_crypto/src/olm/signing/mod.rs new file mode 100644 index 00000000..3cc9e3c0 --- /dev/null +++ b/matrix_sdk_crypto/src/olm/signing/mod.rs @@ -0,0 +1,600 @@ +// Copyright 2020 The Matrix.org Foundation C.I.C. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +mod pk_signing; + +use serde::{Deserialize, Serialize}; +use serde_json::Error as JsonError; +use std::{ + collections::BTreeMap, + sync::{ + atomic::{AtomicBool, Ordering}, + Arc, + }, +}; + +use matrix_sdk_common::{ + api::r0::keys::{upload_signatures::Request as SignatureUploadRequest, KeyUsage}, + encryption::DeviceKeys, + identifiers::{DeviceKeyAlgorithm, DeviceKeyId, UserId}, + locks::Mutex, +}; + +use crate::{ + error::SignatureError, requests::UploadSigningKeysRequest, OwnUserIdentity, ReadOnlyAccount, + ReadOnlyDevice, UserIdentity, +}; + +use pk_signing::{MasterSigning, PickledSignings, SelfSigning, Signing, SigningError, UserSigning}; + +/// Private cross signing identity. +/// +/// This object holds the private and public ed25519 key triplet that is used +/// for cross signing. +/// +/// The object might be comletely empty or have only some of the key pairs +/// available. +/// +/// It can be used to sign devices or other identities. +#[derive(Clone, Debug)] +pub struct PrivateCrossSigningIdentity { + user_id: Arc, + shared: Arc, + pub(crate) master_key: Arc>>, + pub(crate) user_signing_key: Arc>>, + pub(crate) self_signing_key: Arc>>, +} + +/// The pickled version of a `PrivateCrossSigningIdentity`. +/// +/// Can be used to store the identity. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct PickledCrossSigningIdentity { + /// The user id of the identity owner. + pub user_id: UserId, + /// Have the public keys of the identity been shared. + pub shared: bool, + /// The encrypted pickle of the identity. + pub pickle: String, +} + +impl PrivateCrossSigningIdentity { + /// Get the user id that this identity belongs to. + pub fn user_id(&self) -> &UserId { + &self.user_id + } + + /// Is the identity empty. + /// + /// An empty identity doesn't contain any private keys. + /// + /// It is usual for the identity not to contain the master key since the + /// master key is only needed to sign the subkeys. + /// + /// An empty identity indicates that either no identity was created for this + /// use or that another device created it and hasn't shared it yet with us. + pub async fn is_empty(&self) -> bool { + let has_master = self.master_key.lock().await.is_some(); + let has_user = self.user_signing_key.lock().await.is_some(); + let has_self = self.self_signing_key.lock().await.is_some(); + + !(has_master && has_user && has_self) + } + + /// Create a new empty identity. + pub(crate) fn empty(user_id: UserId) -> Self { + Self { + user_id: Arc::new(user_id), + shared: Arc::new(AtomicBool::new(false)), + master_key: Arc::new(Mutex::new(None)), + self_signing_key: Arc::new(Mutex::new(None)), + user_signing_key: Arc::new(Mutex::new(None)), + } + } + + pub(crate) async fn as_public_identity(&self) -> Result { + let master = self + .master_key + .lock() + .await + .as_ref() + .ok_or(SignatureError::MissingSigningKey)? + .public_key + .clone(); + let self_signing = self + .self_signing_key + .lock() + .await + .as_ref() + .ok_or(SignatureError::MissingSigningKey)? + .public_key + .clone(); + let user_signing = self + .user_signing_key + .lock() + .await + .as_ref() + .ok_or(SignatureError::MissingSigningKey)? + .public_key + .clone(); + let identity = OwnUserIdentity::new(master, self_signing, user_signing)?; + identity.mark_as_verified(); + + Ok(identity) + } + + /// Sign the given public user identity with this private identity. + #[allow(dead_code)] + pub(crate) async fn sign_user( + &self, + user_identity: &UserIdentity, + ) -> Result { + let signed_keys = self + .user_signing_key + .lock() + .await + .as_ref() + .ok_or(SignatureError::MissingSigningKey)? + .sign_user(&user_identity) + .await?; + + Ok(SignatureUploadRequest { signed_keys }) + } + + /// Sign the given device keys with this identity. + pub(crate) async fn sign_device( + &self, + device: &ReadOnlyDevice, + ) -> Result { + let mut device_keys = device.as_device_keys(); + device_keys.signatures.clear(); + self.sign_device_keys(&mut device_keys).await + } + + /// Sign an Olm account with this private identity. + pub(crate) async fn sign_account( + &self, + account: &ReadOnlyAccount, + ) -> Result { + let mut device_keys = account.unsigned_device_keys(); + self.sign_device_keys(&mut device_keys).await + } + + pub(crate) async fn sign_device_keys( + &self, + mut device_keys: &mut DeviceKeys, + ) -> Result { + self.self_signing_key + .lock() + .await + .as_ref() + .ok_or(SignatureError::MissingSigningKey)? + .sign_device(&mut device_keys) + .await?; + + let mut signed_keys = BTreeMap::new(); + signed_keys + .entry((&*self.user_id).to_owned()) + .or_insert_with(BTreeMap::new) + .insert( + device_keys.device_id.to_string(), + serde_json::to_value(device_keys)?, + ); + + Ok(SignatureUploadRequest { signed_keys }) + } + + /// Create a new identity for the given Olm Account. + /// + /// Returns the new identity, the upload signing keys request and a + /// signature upload request that contains the signature of the account + /// signed by the self signing key. + /// + /// # Arguments + /// + /// * `account` - The Olm account that is creating the new identity. The + /// account will sign the master key and the self signing key will sign the + /// account. + pub(crate) async fn new_with_account( + account: &ReadOnlyAccount, + ) -> (Self, UploadSigningKeysRequest, SignatureUploadRequest) { + let master = Signing::new(); + + let mut public_key = + master.cross_signing_key(account.user_id().to_owned(), KeyUsage::Master); + let signature = account + .sign_json( + serde_json::to_value(&public_key) + .expect("Can't convert own public master key to json"), + ) + .await + .expect("Can't sign own public master key"); + public_key + .signatures + .entry(account.user_id().to_owned()) + .or_insert_with(BTreeMap::new) + .insert( + DeviceKeyId::from_parts(DeviceKeyAlgorithm::Ed25519, account.device_id()) + .to_string(), + signature, + ); + + let master = MasterSigning { + inner: master, + public_key: public_key.into(), + }; + + let identity = Self::new_helper(account.user_id(), master).await; + let signature_request = identity + .sign_account(account) + .await + .expect("Can't sign own device with new cross signign keys"); + + let request = identity.as_upload_request().await; + + (identity, request, signature_request) + } + + async fn new_helper(user_id: &UserId, master: MasterSigning) -> Self { + let user = Signing::new(); + let mut public_key = user.cross_signing_key(user_id.to_owned(), KeyUsage::UserSigning); + master.sign_subkey(&mut public_key).await; + + let user = UserSigning { + inner: user, + public_key: public_key.into(), + }; + + let self_signing = Signing::new(); + let mut public_key = + self_signing.cross_signing_key(user_id.to_owned(), KeyUsage::SelfSigning); + master.sign_subkey(&mut public_key).await; + + let self_signing = SelfSigning { + inner: self_signing, + public_key: public_key.into(), + }; + + Self { + user_id: Arc::new(user_id.to_owned()), + shared: Arc::new(AtomicBool::new(false)), + master_key: Arc::new(Mutex::new(Some(master))), + self_signing_key: Arc::new(Mutex::new(Some(self_signing))), + user_signing_key: Arc::new(Mutex::new(Some(user))), + } + } + + /// Create a new cross signing identity without signing the device that + /// created it. + #[cfg(test)] + pub(crate) async fn new(user_id: UserId) -> Self { + let master = Signing::new(); + + let public_key = master.cross_signing_key(user_id.clone(), KeyUsage::Master); + let master = MasterSigning { + inner: master, + public_key: public_key.into(), + }; + + Self::new_helper(&user_id, master).await + } + + /// Mark the identity as shared. + pub fn mark_as_shared(&self) { + self.shared.store(true, Ordering::SeqCst) + } + + /// Has the identity been shared. + /// + /// A shared identity here means that the public keys of the identity have + /// been uploaded to the server. + pub fn shared(&self) -> bool { + self.shared.load(Ordering::SeqCst) + } + + /// Store the cross signing identity as a pickle. + /// + /// # Arguments + /// + /// * `pickle_key` - The key that should be used to encrypt the signing + /// object, must be 32 bytes long. + /// + /// # Panics + /// + /// This will panic if the provided pickle key isn't 32 bytes long. + pub async fn pickle( + &self, + pickle_key: &[u8], + ) -> Result { + let master_key = if let Some(m) = self.master_key.lock().await.as_ref() { + Some(m.pickle(pickle_key).await) + } else { + None + }; + + let self_signing_key = if let Some(m) = self.self_signing_key.lock().await.as_ref() { + Some(m.pickle(pickle_key).await) + } else { + None + }; + + let user_signing_key = if let Some(m) = self.user_signing_key.lock().await.as_ref() { + Some(m.pickle(pickle_key).await) + } else { + None + }; + + let pickle = PickledSignings { + master_key, + user_signing_key, + self_signing_key, + }; + + let pickle = serde_json::to_string(&pickle)?; + + Ok(PickledCrossSigningIdentity { + user_id: self.user_id.as_ref().to_owned(), + shared: self.shared(), + pickle, + }) + } + + /// Restore the private cross signing identity from a pickle. + /// + /// # Panic + /// + /// Panics if the pickle_key isn't 32 bytes long. + pub async fn from_pickle( + pickle: PickledCrossSigningIdentity, + pickle_key: &[u8], + ) -> Result { + let signings: PickledSignings = serde_json::from_str(&pickle.pickle)?; + + let master = if let Some(m) = signings.master_key { + Some(MasterSigning::from_pickle(m, pickle_key)?) + } else { + None + }; + + let self_signing = if let Some(s) = signings.self_signing_key { + Some(SelfSigning::from_pickle(s, pickle_key)?) + } else { + None + }; + + let user_signing = if let Some(u) = signings.user_signing_key { + Some(UserSigning::from_pickle(u, pickle_key)?) + } else { + None + }; + + Ok(Self { + user_id: Arc::new(pickle.user_id), + shared: Arc::new(AtomicBool::from(pickle.shared)), + master_key: Arc::new(Mutex::new(master)), + self_signing_key: Arc::new(Mutex::new(self_signing)), + user_signing_key: Arc::new(Mutex::new(user_signing)), + }) + } + + /// Get the upload request that is needed to share the public keys of this + /// identity. + pub(crate) async fn as_upload_request(&self) -> UploadSigningKeysRequest { + let master_key = self + .master_key + .lock() + .await + .as_ref() + .cloned() + .map(|k| k.public_key.into()); + + let user_signing_key = self + .user_signing_key + .lock() + .await + .as_ref() + .cloned() + .map(|k| k.public_key.into()); + + let self_signing_key = self + .self_signing_key + .lock() + .await + .as_ref() + .cloned() + .map(|k| k.public_key.into()); + + UploadSigningKeysRequest { + master_key, + user_signing_key, + self_signing_key, + } + } +} + +#[cfg(test)] +mod test { + use crate::{ + identities::{ReadOnlyDevice, UserIdentity}, + olm::ReadOnlyAccount, + }; + use std::{collections::BTreeMap, sync::Arc}; + + use super::{PrivateCrossSigningIdentity, Signing}; + + use matrix_sdk_common::{ + api::r0::keys::CrossSigningKey, + identifiers::{user_id, UserId}, + }; + use matrix_sdk_test::async_test; + + fn user_id() -> UserId { + user_id!("@example:localhost") + } + + fn pickle_key() -> &'static [u8] { + &[0u8; 32] + } + + #[test] + fn signing_creation() { + let signing = Signing::new(); + assert!(!signing.public_key().as_str().is_empty()); + } + + #[async_test] + async fn signature_verification() { + let signing = Signing::new(); + + let message = "Hello world"; + + let signature = signing.sign(message).await; + assert!(signing.verify(message, &signature).await.is_ok()); + } + + #[async_test] + async fn pickling_signing() { + let signing = Signing::new(); + let pickled = signing.pickle(pickle_key()).await; + + let unpickled = Signing::from_pickle(pickled, pickle_key()).unwrap(); + + assert_eq!(signing.public_key(), unpickled.public_key()); + } + + #[async_test] + async fn private_identity_creation() { + let identity = PrivateCrossSigningIdentity::new(user_id()).await; + + let master_key = identity.master_key.lock().await; + let master_key = master_key.as_ref().unwrap(); + + assert!(master_key + .public_key + .verify_subkey( + &identity + .self_signing_key + .lock() + .await + .as_ref() + .unwrap() + .public_key, + ) + .is_ok()); + + assert!(master_key + .public_key + .verify_subkey( + &identity + .user_signing_key + .lock() + .await + .as_ref() + .unwrap() + .public_key, + ) + .is_ok()); + } + + #[async_test] + async fn identity_pickling() { + let identity = PrivateCrossSigningIdentity::new(user_id()).await; + + let pickled = identity.pickle(pickle_key()).await.unwrap(); + + let unpickled = PrivateCrossSigningIdentity::from_pickle(pickled, pickle_key()) + .await + .unwrap(); + + assert_eq!(identity.user_id, unpickled.user_id); + assert_eq!( + &*identity.master_key.lock().await, + &*unpickled.master_key.lock().await + ); + assert_eq!( + &*identity.user_signing_key.lock().await, + &*unpickled.user_signing_key.lock().await + ); + assert_eq!( + &*identity.self_signing_key.lock().await, + &*unpickled.self_signing_key.lock().await + ); + } + + #[async_test] + async fn private_identity_signed_by_accound() { + let account = ReadOnlyAccount::new(&user_id(), "DEVICEID".into()); + let (identity, _, _) = PrivateCrossSigningIdentity::new_with_account(&account).await; + let master = identity.master_key.lock().await; + let master = master.as_ref().unwrap(); + + assert!(!master.public_key.signatures().is_empty()); + } + + #[async_test] + async fn sign_device() { + let account = ReadOnlyAccount::new(&user_id(), "DEVICEID".into()); + let (identity, _, _) = PrivateCrossSigningIdentity::new_with_account(&account).await; + + let mut device = ReadOnlyDevice::from_account(&account).await; + let self_signing = identity.self_signing_key.lock().await; + let self_signing = self_signing.as_ref().unwrap(); + + let mut device_keys = device.as_device_keys(); + self_signing.sign_device(&mut device_keys).await.unwrap(); + device.signatures = Arc::new(device_keys.signatures); + + let public_key = &self_signing.public_key; + public_key.verify_device(&device).unwrap() + } + + #[async_test] + async fn sign_user_identity() { + let account = ReadOnlyAccount::new(&user_id(), "DEVICEID".into()); + let (identity, _, _) = PrivateCrossSigningIdentity::new_with_account(&account).await; + + let bob_account = ReadOnlyAccount::new(&user_id!("@bob:localhost"), "DEVICEID".into()); + let (bob_private, _, _) = PrivateCrossSigningIdentity::new_with_account(&bob_account).await; + let mut bob_public = UserIdentity::from_private(&bob_private).await; + + let user_signing = identity.user_signing_key.lock().await; + let user_signing = user_signing.as_ref().unwrap(); + + let signatures = user_signing.sign_user(&bob_public).await.unwrap(); + + let (key_id, signature) = signatures + .iter() + .next() + .unwrap() + .1 + .iter() + .next() + .map(|(k, s)| (k.to_string(), serde_json::from_value(s.to_owned()).unwrap())) + .unwrap(); + + let mut master: CrossSigningKey = bob_public.master_key.as_ref().clone(); + master + .signatures + .entry(identity.user_id().to_owned()) + .or_insert_with(BTreeMap::new) + .insert(key_id, signature); + + bob_public.master_key = master.into(); + + user_signing + .public_key + .verify_master_key(bob_public.master_key()) + .unwrap(); + } +} diff --git a/matrix_sdk_crypto/src/olm/signing/pk_signing.rs b/matrix_sdk_crypto/src/olm/signing/pk_signing.rs new file mode 100644 index 00000000..838dca56 --- /dev/null +++ b/matrix_sdk_crypto/src/olm/signing/pk_signing.rs @@ -0,0 +1,417 @@ +// 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. + +use aes_gcm::{ + aead::{generic_array::GenericArray, Aead, NewAead}, + Aes256Gcm, +}; +use base64::{decode_config, encode_config, DecodeError, URL_SAFE_NO_PAD}; +use getrandom::getrandom; +use matrix_sdk_common::{ + encryption::DeviceKeys, + identifiers::{DeviceKeyAlgorithm, DeviceKeyId}, +}; +use serde::{Deserialize, Serialize}; +use serde_json::{Error as JsonError, Value}; +use std::{collections::BTreeMap, sync::Arc}; +use thiserror::Error; +use zeroize::Zeroizing; + +use olm_rs::pk::OlmPkSigning; + +#[cfg(test)] +use olm_rs::{errors::OlmUtilityError, utility::OlmUtility}; + +use matrix_sdk_common::{ + api::r0::keys::{CrossSigningKey, KeyUsage}, + identifiers::UserId, + locks::Mutex, +}; + +use crate::{ + error::SignatureError, + identities::{MasterPubkey, SelfSigningPubkey, UserSigningPubkey}, + UserIdentity, +}; + +const NONCE_SIZE: usize = 12; + +fn encode>(input: T) -> String { + encode_config(input, URL_SAFE_NO_PAD) +} + +fn decode>(input: T) -> Result, DecodeError> { + decode_config(input, URL_SAFE_NO_PAD) +} + +/// Error type reporting failures in the Signign operations. +#[derive(Debug, Error)] +pub enum SigningError { + /// Error decoding the base64 encoded pickle data. + #[error(transparent)] + Decode(#[from] DecodeError), + + /// Error decrypting the pickled signing seed + #[error("Error decrypting the pickled signign seed")] + Decryption(String), + + /// Error deserializing the pickle data. + #[error(transparent)] + Json(#[from] JsonError), +} + +#[derive(Clone)] +pub struct Signing { + inner: Arc>, + seed: Arc>>, + public_key: PublicSigningKey, +} + +impl std::fmt::Debug for Signing { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("Signing") + .field("public_key", &self.public_key.as_str()) + .finish() + } +} + +impl PartialEq for Signing { + fn eq(&self, other: &Signing) -> bool { + self.seed == other.seed + } +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +struct InnerPickle { + version: u8, + nonce: String, + ciphertext: String, +} + +#[derive(Clone, PartialEq, Debug)] +pub struct MasterSigning { + pub inner: Signing, + pub public_key: MasterPubkey, +} + +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct PickledMasterSigning { + pickle: PickledSigning, + public_key: CrossSigningKey, +} + +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct PickledUserSigning { + pickle: PickledSigning, + public_key: CrossSigningKey, +} + +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct PickledSelfSigning { + pickle: PickledSigning, + public_key: CrossSigningKey, +} + +impl Signature { + #[cfg(test)] + pub fn as_str(&self) -> &str { + &self.0 + } +} + +impl PickledSigning { + pub fn as_str(&self) -> &str { + &self.0 + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct PublicSigningKey(Arc); + +impl PublicSigningKey { + pub fn as_str(&self) -> &str { + &self.0 + } + + #[allow(clippy::inherent_to_string)] + fn to_string(&self) -> String { + self.0.to_string() + } +} + +impl MasterSigning { + pub async fn pickle(&self, pickle_key: &[u8]) -> PickledMasterSigning { + let pickle = self.inner.pickle(pickle_key).await; + let public_key = self.public_key.clone().into(); + PickledMasterSigning { pickle, public_key } + } + + pub fn from_pickle( + pickle: PickledMasterSigning, + pickle_key: &[u8], + ) -> Result { + let inner = Signing::from_pickle(pickle.pickle, pickle_key)?; + + Ok(Self { + inner, + public_key: pickle.public_key.into(), + }) + } + + pub async fn sign_subkey<'a>(&self, subkey: &mut CrossSigningKey) { + // TODO create a borrowed version of a cross singing key. + let subkey_wihtout_signatures = CrossSigningKey { + user_id: subkey.user_id.clone(), + keys: subkey.keys.clone(), + usage: subkey.usage.clone(), + signatures: BTreeMap::new(), + }; + + let message = cjson::to_string(&subkey_wihtout_signatures) + .expect("Can't serialize cross signing subkey"); + let signature = self.inner.sign(&message).await; + + subkey + .signatures + .entry(self.public_key.user_id().to_owned()) + .or_insert_with(BTreeMap::new) + .insert( + DeviceKeyId::from_parts( + DeviceKeyAlgorithm::Ed25519, + self.inner.public_key().as_str().into(), + ) + .to_string(), + signature.0, + ); + } +} + +impl UserSigning { + pub async fn pickle(&self, pickle_key: &[u8]) -> PickledUserSigning { + let pickle = self.inner.pickle(pickle_key).await; + let public_key = self.public_key.clone().into(); + PickledUserSigning { pickle, public_key } + } + + #[allow(dead_code)] + pub async fn sign_user( + &self, + user: &UserIdentity, + ) -> Result>, SignatureError> { + let user_master: &CrossSigningKey = user.master_key().as_ref(); + let signature = self + .inner + .sign_json(serde_json::to_value(user_master)?) + .await?; + + let mut signatures = BTreeMap::new(); + + signatures + .entry(self.public_key.user_id().to_owned()) + .or_insert_with(BTreeMap::new) + .insert( + DeviceKeyId::from_parts( + DeviceKeyAlgorithm::Ed25519, + self.inner.public_key.as_str().into(), + ) + .to_string(), + serde_json::to_value(signature.0)?, + ); + + Ok(signatures) + } + + pub fn from_pickle( + pickle: PickledUserSigning, + pickle_key: &[u8], + ) -> Result { + let inner = Signing::from_pickle(pickle.pickle, pickle_key)?; + + Ok(Self { + inner, + public_key: pickle.public_key.into(), + }) + } +} + +impl SelfSigning { + pub async fn pickle(&self, pickle_key: &[u8]) -> PickledSelfSigning { + let pickle = self.inner.pickle(pickle_key).await; + let public_key = self.public_key.clone().into(); + PickledSelfSigning { pickle, public_key } + } + + pub async fn sign_device_helper(&self, value: Value) -> Result { + self.inner.sign_json(value).await + } + + pub async fn sign_device(&self, device_keys: &mut DeviceKeys) -> Result<(), SignatureError> { + let json_device = serde_json::to_value(&device_keys)?; + let signature = self.sign_device_helper(json_device).await?; + + device_keys + .signatures + .entry(self.public_key.user_id().to_owned()) + .or_insert_with(BTreeMap::new) + .insert( + DeviceKeyId::from_parts( + DeviceKeyAlgorithm::Ed25519, + self.inner.public_key.as_str().into(), + ), + signature.0, + ); + + Ok(()) + } + + pub fn from_pickle( + pickle: PickledSelfSigning, + pickle_key: &[u8], + ) -> Result { + let inner = Signing::from_pickle(pickle.pickle, pickle_key)?; + + Ok(Self { + inner, + public_key: pickle.public_key.into(), + }) + } +} + +#[derive(Clone, PartialEq, Debug)] +pub struct SelfSigning { + pub inner: Signing, + pub public_key: SelfSigningPubkey, +} + +#[derive(Clone, PartialEq, Debug)] +pub struct UserSigning { + pub inner: Signing, + pub public_key: UserSigningPubkey, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct PickledSignings { + pub master_key: Option, + pub user_signing_key: Option, + pub self_signing_key: Option, +} + +#[derive(Debug, Clone)] +pub struct Signature(String); + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct PickledSigning(String); + +impl Signing { + pub fn new() -> Self { + let seed = OlmPkSigning::generate_seed(); + Self::from_seed(seed) + } + + pub fn from_seed(seed: Vec) -> Self { + let inner = OlmPkSigning::new(seed.clone()).expect("Unable to create pk signing object"); + let public_key = PublicSigningKey(Arc::new(inner.public_key().to_owned())); + + Signing { + inner: Arc::new(Mutex::new(inner)), + seed: Arc::new(Zeroizing::from(seed)), + public_key, + } + } + + pub fn from_pickle(pickle: PickledSigning, pickle_key: &[u8]) -> Result { + let pickled: InnerPickle = serde_json::from_str(pickle.as_str())?; + + let key = GenericArray::from_slice(pickle_key); + let cipher = Aes256Gcm::new(key); + + let nonce = decode(pickled.nonce)?; + let nonce = GenericArray::from_slice(&nonce); + let ciphertext = &decode(pickled.ciphertext)?; + + let seed = cipher + .decrypt(&nonce, ciphertext.as_slice()) + .map_err(|e| SigningError::Decryption(e.to_string()))?; + + Ok(Self::from_seed(seed)) + } + + pub async fn pickle(&self, pickle_key: &[u8]) -> PickledSigning { + let key = GenericArray::from_slice(pickle_key); + let cipher = Aes256Gcm::new(key); + + let mut nonce = vec![0u8; NONCE_SIZE]; + getrandom(&mut nonce).expect("Can't generate nonce to pickle the signing object"); + let nonce = GenericArray::from_slice(nonce.as_slice()); + + let ciphertext = cipher + .encrypt(nonce, self.seed.as_slice()) + .expect("Can't encrypt signing pickle"); + + let ciphertext = encode(ciphertext); + + let pickle = InnerPickle { + version: 1, + nonce: encode(nonce.as_slice()), + ciphertext, + }; + + PickledSigning(serde_json::to_string(&pickle).expect("Can't encode pickled signing")) + } + + pub fn public_key(&self) -> &PublicSigningKey { + &self.public_key + } + + pub fn cross_signing_key(&self, user_id: UserId, usage: KeyUsage) -> CrossSigningKey { + let mut keys = BTreeMap::new(); + + keys.insert( + DeviceKeyId::from_parts( + DeviceKeyAlgorithm::Ed25519, + self.public_key().as_str().into(), + ) + .to_string(), + self.public_key().to_string(), + ); + + CrossSigningKey { + user_id, + usage: vec![usage], + keys, + signatures: BTreeMap::new(), + } + } + + #[cfg(test)] + pub async fn verify( + &self, + message: &str, + signature: &Signature, + ) -> Result { + let utility = OlmUtility::new(); + utility.ed25519_verify(self.public_key.as_str(), message, signature.as_str()) + } + + pub async fn sign_json(&self, mut json: Value) -> Result { + let json_object = json.as_object_mut().ok_or(SignatureError::NotAnObject)?; + let _ = json_object.remove("signatures"); + let canonical_json = cjson::to_string(json_object)?; + Ok(self.sign(&canonical_json).await) + } + + pub async fn sign(&self, message: &str) -> Signature { + Signature(self.inner.lock().await.sign(message)) + } +} diff --git a/matrix_sdk_crypto/src/requests.rs b/matrix_sdk_crypto/src/requests.rs index 5b70c744..fb619018 100644 --- a/matrix_sdk_crypto/src/requests.rs +++ b/matrix_sdk_crypto/src/requests.rs @@ -20,6 +20,9 @@ use matrix_sdk_common::{ claim_keys::Response as KeysClaimResponse, get_keys::Response as KeysQueryResponse, upload_keys::{Request as KeysUploadRequest, Response as KeysUploadResponse}, + upload_signatures::{ + Request as SignatureUploadRequest, Response as SignatureUploadResponse, + }, upload_signing_keys::Response as SigningKeysUploadResponse, CrossSigningKey, }, @@ -114,6 +117,9 @@ pub enum OutgoingRequests { /// things, the main use is key requests/forwards and interactive device /// verification. ToDeviceRequest(ToDeviceRequest), + /// Signature upload request, this request is used after a successful device + /// or user verification is done. + SignatureUpload(SignatureUploadRequest), } #[cfg(test)] @@ -144,6 +150,12 @@ impl From for OutgoingRequests { } } +impl From for OutgoingRequests { + fn from(request: SignatureUploadRequest) -> Self { + OutgoingRequests::SignatureUpload(request) + } +} + /// Enum over all the incoming responses we need to receive. #[derive(Debug)] pub enum IncomingResponse<'a> { @@ -161,6 +173,9 @@ pub enum IncomingResponse<'a> { /// The cross signing keys upload response, marking our private cross /// signing identity as shared. SigningKeysUpload(&'a SigningKeysUploadResponse), + /// The cross signing keys upload response, marking our private cross + /// signing identity as shared. + SignatureUpload(&'a SignatureUploadResponse), } impl<'a> From<&'a KeysUploadResponse> for IncomingResponse<'a> { @@ -187,6 +202,12 @@ impl<'a> From<&'a KeysClaimResponse> for IncomingResponse<'a> { } } +impl<'a> From<&'a SignatureUploadResponse> for IncomingResponse<'a> { + fn from(response: &'a SignatureUploadResponse) -> Self { + IncomingResponse::SignatureUpload(response) + } +} + /// Outgoing request type, holds the unique ID of the request and the actual /// request. #[derive(Debug, Clone)] diff --git a/matrix_sdk_crypto/src/session_manager/sessions.rs b/matrix_sdk_crypto/src/session_manager/sessions.rs index 29af3b6f..8e18dfb7 100644 --- a/matrix_sdk_crypto/src/session_manager/sessions.rs +++ b/matrix_sdk_crypto/src/session_manager/sessions.rs @@ -354,12 +354,13 @@ mod test { let identity = Arc::new(Mutex::new(PrivateCrossSigningIdentity::empty( user_id.clone(), ))); - let verification = VerificationMachine::new(account.clone(), identity, store.clone()); + let verification = + VerificationMachine::new(account.clone(), identity.clone(), store.clone()); let user_id = Arc::new(user_id); let device_id = Arc::new(device_id); - let store = Store::new(user_id.clone(), store, verification); + let store = Store::new(user_id.clone(), identity, store, verification); let account = Account { inner: account, diff --git a/matrix_sdk_crypto/src/store/mod.rs b/matrix_sdk_crypto/src/store/mod.rs index b19e3b07..3fda11ba 100644 --- a/matrix_sdk_crypto/src/store/mod.rs +++ b/matrix_sdk_crypto/src/store/mod.rs @@ -98,6 +98,7 @@ pub type Result = std::result::Result; #[derive(Debug, Clone)] pub(crate) struct Store { user_id: Arc, + identity: Arc>, inner: Arc>, verification_machine: VerificationMachine, } @@ -130,11 +131,13 @@ pub struct DeviceChanges { impl Store { pub fn new( user_id: Arc, + identity: Arc>, store: Arc>, verification_machine: VerificationMachine, ) -> Self { Self { user_id, + identity, inner: store, verification_machine, } @@ -216,6 +219,7 @@ impl Store { Ok(UserDevices { inner: devices, + private_identity: self.identity.clone(), verification_machine: self.verification_machine.clone(), own_identity, device_owner_identity, @@ -240,6 +244,7 @@ impl Store { .await? .map(|d| Device { inner: d, + private_identity: self.identity.clone(), verification_machine: self.verification_machine.clone(), own_identity, device_owner_identity, diff --git a/matrix_sdk_crypto/src/verification/machine.rs b/matrix_sdk_crypto/src/verification/machine.rs index ba098ddc..c3ba5e88 100644 --- a/matrix_sdk_crypto/src/verification/machine.rs +++ b/matrix_sdk_crypto/src/verification/machine.rs @@ -25,7 +25,7 @@ use matrix_sdk_common::{ uuid::Uuid, }; -use super::sas::{content_to_request, Sas}; +use super::sas::{content_to_request, Sas, VerificationResult}; use crate::{ olm::PrivateCrossSigningIdentity, requests::{OutgoingRequest, ToDeviceRequest}, @@ -36,7 +36,7 @@ use crate::{ #[derive(Clone, Debug)] pub struct VerificationMachine { account: ReadOnlyAccount, - user_identity: Arc>, + private_identity: Arc>, pub(crate) store: Arc>, verifications: Arc>, outgoing_to_device_messages: Arc>, @@ -50,7 +50,7 @@ impl VerificationMachine { ) -> Self { Self { account, - user_identity: identity, + private_identity: identity, store, verifications: Arc::new(DashMap::new()), outgoing_to_device_messages: Arc::new(DashMap::new()), @@ -62,9 +62,11 @@ impl VerificationMachine { device: ReadOnlyDevice, ) -> Result<(Sas, ToDeviceRequest), CryptoStoreError> { let identity = self.store.get_user_identity(device.user_id()).await?; + let private_identity = self.private_identity.lock().await.clone(); let (sas, content) = Sas::start( self.account.clone(), + private_identity, device.clone(), self.store.clone(), identity, @@ -158,8 +160,10 @@ impl VerificationMachine { .get_device(&e.sender, &e.content.from_device) .await? { + let private_identity = self.private_identity.lock().await.clone(); match Sas::from_start_event( self.account.clone(), + private_identity, d, self.store.clone(), e, @@ -202,14 +206,28 @@ impl VerificationMachine { self.receive_event_helper(&s, event); if s.is_done() { - if let Some(r) = s.mark_as_done().await? { - self.outgoing_to_device_messages.insert( - r.txn_id, - OutgoingRequest { - request_id: r.txn_id, - request: Arc::new(r.into()), - }, - ); + match s.mark_as_done().await? { + VerificationResult::Ok => (), + VerificationResult::Cancel(r) => { + self.outgoing_to_device_messages.insert( + r.txn_id, + OutgoingRequest { + request_id: r.txn_id, + request: Arc::new(r.into()), + }, + ); + } + VerificationResult::SignatureUpload(r) => { + let request_id = Uuid::new_v4(); + + self.outgoing_to_device_messages.insert( + request_id, + OutgoingRequest { + request_id, + request: Arc::new(r.into()), + }, + ); + } } } }; @@ -275,7 +293,13 @@ mod test { let bob_store: Arc> = Arc::new(Box::new(bob_store)); let identity = Arc::new(Mutex::new(PrivateCrossSigningIdentity::empty(alice_id()))); let machine = VerificationMachine::new(alice, identity, Arc::new(Box::new(store))); - let (bob_sas, start_content) = Sas::start(bob, alice_device, bob_store, None); + let (bob_sas, start_content) = Sas::start( + bob, + PrivateCrossSigningIdentity::empty(bob_id()), + alice_device, + bob_store, + None, + ); machine .receive_event(&mut wrap_any_to_device_content( bob_sas.user_id(), @@ -342,13 +366,13 @@ mod test { let mut event = wrap_any_to_device_content( alice.user_id(), - get_content_from_request(&alice.confirm().await.unwrap().unwrap()), + get_content_from_request(&alice.confirm().await.unwrap().0.unwrap()), ); bob.receive_event(&mut event); let mut event = wrap_any_to_device_content( bob.user_id(), - get_content_from_request(&bob.confirm().await.unwrap().unwrap()), + get_content_from_request(&bob.confirm().await.unwrap().0.unwrap()), ); alice.receive_event(&mut event); diff --git a/matrix_sdk_crypto/src/verification/mod.rs b/matrix_sdk_crypto/src/verification/mod.rs index 7083c760..d93dc8ec 100644 --- a/matrix_sdk_crypto/src/verification/mod.rs +++ b/matrix_sdk_crypto/src/verification/mod.rs @@ -16,7 +16,7 @@ mod machine; mod sas; pub use machine::VerificationMachine; -pub use sas::Sas; +pub use sas::{Sas, VerificationResult}; #[cfg(test)] pub(crate) mod test { diff --git a/matrix_sdk_crypto/src/verification/sas/mod.rs b/matrix_sdk_crypto/src/verification/sas/mod.rs index b71070a7..9ef25bb8 100644 --- a/matrix_sdk_crypto/src/verification/sas/mod.rs +++ b/matrix_sdk_crypto/src/verification/sas/mod.rs @@ -19,9 +19,10 @@ mod sas_state; use std::time::Instant; use std::sync::{Arc, Mutex}; -use tracing::{info, trace, warn}; +use tracing::{error, info, trace, warn}; use matrix_sdk_common::{ + api::r0::keys::upload_signatures::Request as SignatureUploadRequest, events::{ key::verification::{ accept::AcceptEventContent, cancel::CancelCode, mac::MacEventContent, @@ -33,7 +34,9 @@ use matrix_sdk_common::{ }; use crate::{ + error::SignatureError, identities::{LocalTrust, ReadOnlyDevice, UserIdentities}, + olm::PrivateCrossSigningIdentity, store::{Changes, CryptoStore, CryptoStoreError, DeviceChanges}, ReadOnlyAccount, ToDeviceRequest, }; @@ -43,12 +46,24 @@ use sas_state::{ Accepted, Canceled, Confirmed, Created, Done, KeyReceived, MacReceived, SasState, Started, }; +#[derive(Debug)] +/// A result of a verification flow. +pub enum VerificationResult { + /// The verification succeeded, nothing needs to be done. + Ok, + /// The verification was canceled. + Cancel(ToDeviceRequest), + /// The verification is done and has signatures that need to be uploaded. + SignatureUpload(SignatureUploadRequest), +} + #[derive(Clone, Debug)] /// Short authentication string object. pub struct Sas { inner: Arc>, store: Arc>, account: ReadOnlyAccount, + private_identity: PrivateCrossSigningIdentity, other_device: ReadOnlyDevice, other_identity: Option, flow_id: Arc, @@ -103,6 +118,7 @@ impl Sas { /// sent out through the server to the other device. pub(crate) fn start( account: ReadOnlyAccount, + private_identity: PrivateCrossSigningIdentity, other_device: ReadOnlyDevice, store: Arc>, other_identity: Option, @@ -117,6 +133,7 @@ impl Sas { let sas = Sas { inner: Arc::new(Mutex::new(inner)), account, + private_identity, store, other_device, flow_id, @@ -138,6 +155,7 @@ impl Sas { /// the other side. pub(crate) fn from_start_event( account: ReadOnlyAccount, + private_identity: PrivateCrossSigningIdentity, other_device: ReadOnlyDevice, store: Arc>, event: &ToDeviceEvent, @@ -154,6 +172,7 @@ impl Sas { Ok(Sas { inner: Arc::new(Mutex::new(inner)), account, + private_identity, other_device, other_identity, store, @@ -179,7 +198,9 @@ impl Sas { /// Does nothing if we're not in a state where we can confirm the short auth /// string, otherwise returns a `MacEventContent` that needs to be sent to /// the server. - pub async fn confirm(&self) -> Result, CryptoStoreError> { + pub async fn confirm( + &self, + ) -> Result<(Option, Option), CryptoStoreError> { let (content, done) = { let mut guard = self.inner.lock().unwrap(); let sas: InnerSas = (*guard).clone(); @@ -189,26 +210,52 @@ impl Sas { (content, guard.is_done()) }; - let cancel = if done { - self.mark_as_done().await? - } else { - None - }; + let mac_request = content + .map(|c| self.content_to_request(AnyToDeviceEventContent::KeyVerificationMac(c))); - if cancel.is_some() { - Ok(cancel) + if done { + match self.mark_as_done().await? { + VerificationResult::Cancel(r) => Ok((Some(r), None)), + VerificationResult::Ok => Ok((mac_request, None)), + VerificationResult::SignatureUpload(r) => Ok((mac_request, Some(r))), + } } else { - Ok(content.map(|c| { - let content = AnyToDeviceEventContent::KeyVerificationMac(c); - self.content_to_request(content) - })) + Ok((mac_request, None)) } } - pub(crate) async fn mark_as_done(&self) -> Result, CryptoStoreError> { + pub(crate) async fn mark_as_done(&self) -> Result { if let Some(device) = self.mark_device_as_verified().await? { let identity = self.mark_identity_as_verified().await?; + // We only sign devices of our own user here. + let signature_request = if device.user_id() == self.user_id() { + match self.private_identity.sign_device(&device).await { + Ok(r) => Some(r), + Err(SignatureError::MissingSigningKey) => { + warn!( + "Can't sign the device keys for {} {}, \ + no private user signing key found", + device.user_id(), + device.device_id(), + ); + + None + } + Err(e) => { + error!( + "Error signing device keys for {} {} {:?}", + device.user_id(), + device.device_id(), + e + ); + None + } + } + } else { + None + }; + let mut changes = Changes { devices: DeviceChanges { changed: vec![device], @@ -217,14 +264,68 @@ impl Sas { ..Default::default() }; - if let Some(i) = identity { - changes.identities.changed.push(i); - } + let identity_signature_request = if let Some(i) = identity { + // We only sign other users here. + let request = if let Some(i) = i.other() { + // Signing can fail if the user signing key is missing. + match self.private_identity.sign_user(&i).await { + Ok(r) => Some(r), + Err(SignatureError::MissingSigningKey) => { + warn!( + "Can't sign the public cross signing keys for {}, \ + no private user signing key found", + i.user_id() + ); + None + } + Err(e) => { + error!( + "Error signing the public cross signing keys for {} {:?}", + i.user_id(), + e + ); + None + } + } + } else { + None + }; + changes.identities.changed.push(i); + + request + } else { + None + }; + + // If there are two signature upload requests, merge them. Otherwise + // use the one we have or None. + // + // Realistically at most one reuqest will be used but let's make + // this future proof. + let merged_request = if let Some(mut r) = signature_request { + if let Some(user_request) = identity_signature_request { + r.signed_keys.extend(user_request.signed_keys); + Some(r) + } else { + Some(r) + } + } else if let Some(r) = identity_signature_request { + Some(r) + } else { + None + }; + + // TODO store the request as well. self.store.save_changes(changes).await?; - Ok(None) + Ok(merged_request + .map(VerificationResult::SignatureUpload) + .unwrap_or(VerificationResult::Ok)) } else { - Ok(self.cancel()) + Ok(self + .cancel() + .map(VerificationResult::Cancel) + .unwrap_or(VerificationResult::Ok)) } } @@ -259,9 +360,6 @@ impl Sas { if let UserIdentities::Own(i) = &identity { i.mark_as_verified(); } - // TODO if we have the private part of the user signing - // key we should sign and upload a signature for this - // identity. Ok(Some(identity)) } else { @@ -314,9 +412,6 @@ impl Sas { ); device.set_trust_state(LocalTrust::Verified); - // TODO if this is a device from our own user and we have - // the private part of the self signing key, we should sign - // the device and upload the signature. Ok(Some(device)) } else { @@ -684,6 +779,7 @@ mod test { }; use crate::{ + olm::PrivateCrossSigningIdentity, store::{CryptoStore, MemoryStore}, verification::test::{get_content_from_request, wrap_any_to_device_content}, ReadOnlyAccount, ReadOnlyDevice, @@ -813,10 +909,24 @@ mod test { let bob_store: Arc> = Arc::new(Box::new(bob_store)); - let (alice, content) = Sas::start(alice, bob_device, alice_store, None); + let (alice, content) = Sas::start( + alice, + PrivateCrossSigningIdentity::empty(alice_id()), + bob_device, + alice_store, + None, + ); let event = wrap_to_device_event(alice.user_id(), content); - let bob = Sas::from_start_event(bob, alice_device, bob_store, &event, None).unwrap(); + let bob = Sas::from_start_event( + bob, + PrivateCrossSigningIdentity::empty(bob_id()), + alice_device, + bob_store, + &event, + None, + ) + .unwrap(); let mut event = wrap_any_to_device_content( bob.user_id(), get_content_from_request(&bob.accept().unwrap()), @@ -841,13 +951,13 @@ mod test { let mut event = wrap_any_to_device_content( alice.user_id(), - get_content_from_request(&alice.confirm().await.unwrap().unwrap()), + get_content_from_request(&alice.confirm().await.unwrap().0.unwrap()), ); bob.receive_event(&mut event); let mut event = wrap_any_to_device_content( bob.user_id(), - get_content_from_request(&bob.confirm().await.unwrap().unwrap()), + get_content_from_request(&bob.confirm().await.unwrap().0.unwrap()), ); alice.receive_event(&mut event);