From 674b70f78425efb8b8b697c119ad2a3ff70bc523 Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Mon, 2 Dec 2024 13:02:31 +0100 Subject: [PATCH] Improve error handling --- CMakeLists.txt | 1 - Quotient/connection.cpp | 6 + Quotient/crypto-sdk/src/cryptomachine.rs | 1483 +++++++++++++--------- Quotient/crypto-sdk/src/lib.rs | 98 +- Quotient/crypto-sdk/src/verification.rs | 8 +- 5 files changed, 992 insertions(+), 604 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 79e723f52..216e65975 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -217,7 +217,6 @@ target_sources(${QUOTIENT_LIB_NAME} PUBLIC FILE_SET HEADERS BASE_DIRS . Quotient/jobs/mediathumbnailjob.cpp Quotient/jobs/downloadfilejob.cpp Quotient/keyverificationsession.cpp - Quotient/e2ee/e2ee_common.cpp Quotient/keyimport.cpp libquotientemojis.qrc ) diff --git a/Quotient/connection.cpp b/Quotient/connection.cpp index 5956c929b..215c3ac26 100644 --- a/Quotient/connection.cpp +++ b/Quotient/connection.cpp @@ -395,6 +395,12 @@ void Connection::Private::setupCryptoMachine(const QByteArray& picklingKey) mxIdForDb.replace(u':', u'_'); const QString databasePath{ QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) % u'/' % mxIdForDb % u'/' % q->deviceId() }; cryptoMachine = crypto::init(stringToRust(q->userId()), stringToRust(q->deviceId()), stringToRust(databasePath), bytesToRust(picklingKey)); + if (!(*cryptoMachine)->is_ok()) { + //TODO as part of #782: if crypto machine creation fails, make PendingConnection fail to create a Connection. Then clients can show these errors nicely + // and we don't have to guard all functions for the crypto machine not being available. + qCritical() << "Failed to load crypto machine" << (int) (*cryptoMachine)->error() << stringFromRust((*cryptoMachine)->error_string()); + qApp->exit(1); + } } void Connection::Private::completeSetup(const QString& mxId, bool newLogin, diff --git a/Quotient/crypto-sdk/src/cryptomachine.rs b/Quotient/crypto-sdk/src/cryptomachine.rs index 34a8310de..36c44eed3 100644 --- a/Quotient/crypto-sdk/src/cryptomachine.rs +++ b/Quotient/crypto-sdk/src/cryptomachine.rs @@ -1,7 +1,9 @@ +use matrix_sdk_common::ruma::events::room::history_visibility::HistoryVisibility; +use std::error::Error; use std::{collections::BTreeMap, mem::ManuallyDrop, ops::Deref}; +use matrix_sdk_common::ruma::OwnedTransactionId; use matrix_sdk_common::ruma::{ - UInt, api::client::{ backup::{add_backup_keys, RoomKeyBackup}, keys::{ @@ -23,21 +25,21 @@ use matrix_sdk_common::ruma::{ AnyToDeviceEvent, }, serde::{base64::Standard, Raw}, - DeviceId, DeviceKeyAlgorithm, EventId, OwnedDeviceId, OwnedUserId, RoomId, UserId, + DeviceId, DeviceKeyAlgorithm, EventId, OwnedDeviceId, OwnedUserId, RoomId, UInt, UserId, }; - use matrix_sdk_crypto::types::RoomKeyBackupInfo; use matrix_sdk_crypto::{ secret_storage::{AesHmacSha2EncryptedData, SecretStorageKey}, store::BackupDecryptionKey, - CrossSigningKeyExport, EncryptionSettings, EncryptionSyncChanges, IncomingResponse, OlmMachine, - SasState, UserIdentities, Verification, VerificationRequestState, KeysBackupRequest, + CrossSigningKeyExport, EncryptionSettings, EncryptionSyncChanges, IncomingResponse, + KeysBackupRequest, OlmMachine, SasState, UserIdentities, Verification, + VerificationRequestState, }; use serde::Deserialize; -use matrix_sdk_common::ruma::OwnedTransactionId; use crate::{ encryption_info::EncryptionInfo, + ffi::InitError, request::{ ConfirmRequests, KeyVerificationRequest, KeysClaimRequest, OutgoingKeyVerificationRequest, ToDeviceRequest, @@ -47,6 +49,8 @@ use crate::{ }; pub(crate) struct CryptoMachine { + pub(crate) error: InitError, + pub(crate) error_string: String, pub(crate) runtime: tokio::runtime::Runtime, pub(crate) machine: Option>, } @@ -54,7 +58,10 @@ pub(crate) struct CryptoMachine { impl Drop for CryptoMachine { fn drop(&mut self) { self.runtime.block_on(async { - let machine = self.machine.take().unwrap(); + let machine = self + .machine + .take() + .expect("CryptoMachine should not be destroyed more than once"); drop(ManuallyDrop::into_inner(machine)); }) } @@ -92,53 +99,70 @@ impl SyncChanges { } impl CryptoMachine { + pub(crate) fn is_ok(&self) -> bool { + self.error == InitError::Ok + } + pub(crate) fn error(&self) -> InitError { + self.error.clone() + } + pub(crate) fn error_string(&self) -> String { + self.error_string.clone() + } + pub(crate) fn outgoing_requests(&self) -> Vec { - self.runtime.block_on(async { - match self - .machine - .as_ref() - .expect("Should not happen") - .outgoing_requests() - .await - { - Ok(requests) => requests + let result: Result, Box> = + self.runtime.block_on(async { + Ok(self + .machine + .as_ref() + .ok_or("No crypto machine")? + .outgoing_requests() + .await? .iter() .map(|it| crate::request::OutgoingRequest(it.clone())) - .collect(), - Err(err) => { - println!("Failed to load outgoing requests"); - println!("{:?}", err); - Default::default() - } + .collect()) + }); + match result { + Ok(requests) => requests, + Err(error) => { + eprintln!("Failed to process outgoing_requests: {:?}", error); + panic!(); // TODO } - }) + } } pub(crate) fn mark_keys_upload_as_sent(&mut self, response: String, request_id: String) { - self.runtime.block_on(async { + let result: Result<(), Box> = self.runtime.block_on(async { #[derive(serde::Deserialize)] struct Response { one_time_key_counts: BTreeMap, } - self.machine + let response = serde_json::from_str::(response.as_str())?; + + Ok(self + .machine .as_ref() - .unwrap() + .ok_or("No crypto machine")? .mark_request_as_sent( request_id.as_str().into(), IncomingResponse::KeysUpload(&upload_keys::v3::Response::new( - serde_json::from_str::(response.as_str()) - .unwrap() - .one_time_key_counts, + response.one_time_key_counts, )), ) - .await - .unwrap(); + .await?) }); + match result { + Ok(_) => {} + Err(error) => { + eprintln!("Failed to process mark_keys_upload_as_sent: {:?}", error); + // FIXME: Return an error + } + } } pub(crate) fn mark_keys_query_as_sent(&mut self, response: String, request_id: String) { - self.runtime.block_on(async { + let result: Result<(), Box> = self.runtime.block_on(async { #[derive(Deserialize)] struct Response { failures: Option>, @@ -148,7 +172,7 @@ impl CryptoMachine { self_signing_keys: Option>>, user_signing_keys: Option>>, } - let r: Response = serde_json::from_str(response.as_str()).unwrap(); + let r = serde_json::from_str::(response.as_str())?; let mut response = get_keys::v3::Response::new(); response.failures = r.failures.unwrap_or_default(); response.device_keys = r.device_keys.unwrap_or_default(); @@ -157,118 +181,160 @@ impl CryptoMachine { response.user_signing_keys = r.user_signing_keys.unwrap_or_default(); self.machine .as_ref() - .unwrap() + .expect("Crypto machine must be valid") .mark_request_as_sent( request_id.as_str().into(), IncomingResponse::KeysQuery(&response), ) - .await - .unwrap(); + .await?; + Ok(()) }); + match result { + Ok(_) => {} + Err(error) => { + eprintln!("Failed to process mark_keys_query_as_sent: {:?}", error); + //FIXME: return error + } + } } pub(crate) fn mark_keys_claim_as_sent(&mut self, response: String, request_id: String) { - self.runtime.block_on(async { + let result: Result<(), Box> = self.runtime.block_on(async { #[derive(Deserialize)] struct Response { failures: Option>, one_time_keys: Option>, } - let r: Response = serde_json::from_str(response.as_str()).unwrap(); + let r = serde_json::from_str::(response.as_str())?; let mut request = claim_keys::v3::Response::new(r.one_time_keys.unwrap_or_default()); request.failures = r.failures.unwrap_or_default(); self.machine .as_ref() - .unwrap() + .ok_or("No crypto machine")? .mark_request_as_sent( request_id.as_str().into(), IncomingResponse::KeysClaim(&request), ) - .await - .unwrap(); + .await?; + Ok(()) }); + match result { + Ok(_) => {} + Err(error) => { + eprintln!("Failed to process mark_keys_claim_as_sent: {:?}", error); + //FIXME: return error + } + } } pub(crate) fn mark_to_device_as_sent(&mut self, _: String, request_id: String) { - self.runtime.block_on(async { + let result: Result<(), Box> = self.runtime.block_on(async { self.machine .as_ref() - .unwrap() + .ok_or("No crypto_machine")? .mark_request_as_sent( request_id.as_str().into(), IncomingResponse::ToDevice( &to_device::send_event_to_device::v3::Response::new(/* no content */), ), ) - .await - .unwrap(); + .await?; + Ok(()) }); + match result { + Ok(_) => {} + Err(error) => { + eprintln!("Failed to process mark_to_device_as_sent: {:?}", error); + //FIXME: return error + } + } } pub(crate) fn mark_signature_upload_as_sent(&mut self, response: String, request_id: String) { - self.runtime.block_on(async { + let result: Result<(), Box> = self.runtime.block_on(async { #[derive(Deserialize)] struct Response { failures: Option>>, } - let r: Response = serde_json::from_str(response.as_str()).unwrap(); + let r = serde_json::from_str::(response.as_str())?; let mut request = upload_signatures::v3::Response::new(); request.failures = r.failures.unwrap_or_default(); self.machine .as_ref() - .unwrap() + .ok_or("No crypto machine")? .mark_request_as_sent( request_id.as_str().into(), IncomingResponse::SignatureUpload(&request), ) - .await - .unwrap(); + .await?; + Ok(()) }); + match result { + Ok(_) => {} + Err(error) => { + eprintln!( + "Failed to process mark_signature_upload_as_sent: {:?}", + error + ); + //FIXME: return error + } + } } pub(crate) fn mark_room_message_as_sent(&mut self, event_id: String, request_id: String) { - self.runtime.block_on(async { + let result: Result<(), Box> = self.runtime.block_on(async { + let event_id = EventId::parse(&event_id)?; self.machine .as_ref() - .unwrap() + .ok_or("No crypto machine")? .mark_request_as_sent( request_id.as_str().into(), - IncomingResponse::RoomMessage(&send_message_event::v3::Response::new( - EventId::parse(event_id).unwrap(), - )), + IncomingResponse::RoomMessage(&send_message_event::v3::Response::new(event_id)), ) - .await - .unwrap(); + .await?; + Ok(()) }); + match result { + Ok(_) => {} + Err(error) => { + eprintln!("Failed to process mark_room_message_as_sent: {:?}", error); + //FIXME: return error + } + } } pub(crate) fn mark_keys_backup_as_sent(&mut self, response: String, request_id: String) { - self.runtime.block_on(async { + let result: Result<(), Box> = self.runtime.block_on(async { #[derive(Deserialize)] struct Response { etag: String, count: UInt, } - let r: Response = serde_json::from_str(response.as_str()).unwrap(); + let r = serde_json::from_str::(response.as_str())?; self.machine .as_ref() - .unwrap() + .ok_or("No crypto machine")? .mark_request_as_sent( request_id.as_str().into(), IncomingResponse::KeysBackup(&add_backup_keys::v3::Response::new( r.etag, r.count, )), ) - .await - .unwrap(); + .await?; + Ok(()) }); + match result { + Ok(_) => {} + Err(error) => { + eprintln!("Failed to process mark_keys_backup_as_sent: {:?}", error); + //FIXME: return error + } + } } - pub(crate) fn receive_sync_changes( - &mut self, - sync_json: String, - ) -> Box { - self.runtime.block_on(async { + pub(crate) fn receive_sync_changes(&mut self, sync_json: String) -> Box { + let result: Result, Box> = self.runtime.block_on(async { + let machine = self.machine.as_ref().ok_or("No crypto machine")?; #[derive(serde::Deserialize)] struct Changes { to_device: Option>>>, @@ -278,12 +344,8 @@ impl CryptoMachine { device_unused_fallback_key_types: Option>, } - let changes = serde_json::from_str::(sync_json.as_str()).unwrap(); - - let changes = self - .machine - .as_ref() - .unwrap() + let changes = serde_json::from_str::(sync_json.as_str())?; + let changes = machine .receive_sync_changes(EncryptionSyncChanges { to_device_events: if let Some(events) = changes.to_device { events["events"].clone() @@ -295,28 +357,50 @@ impl CryptoMachine { unused_fallback_keys: changes.device_unused_fallback_key_types.as_deref(), next_batch_token: changes.next_batch, }) - .await - .unwrap(); + .await?; let mut events = vec![]; for to_device_event in changes.0 { - if let AnyToDeviceEvent::KeyVerificationRequest(request) = - to_device_event.deserialize().unwrap() - { - events.push(KeyVerificationRequest( - self.machine - .as_ref() - .unwrap() - .get_verification_request( - &request.sender, - request.content.transaction_id, - ) - .unwrap(), - )); + // NOTE: Do not use question mark operator in for loop. + match to_device_event.deserialize() { + Ok(AnyToDeviceEvent::KeyVerificationRequest(request)) => { + if let Some(request) = machine.get_verification_request( + &request.sender, + request.content.transaction_id.clone(), + ) { + events.push(KeyVerificationRequest(request)); + } else { + eprintln!( + "No request for {} {}", + request.sender, request.content.transaction_id + ); + } + } + Ok(_) => {} + Err(error) => { + eprintln!("Failed to deserialize to_device event: {}", error); + } } } - Box::new(SyncChanges{sessions: events, keys: changes.1.iter().map(|it| Key {session_id: it.session_id.clone(), room_id: it.room_id.to_string()}).collect()}) - }) + Ok(Box::new(SyncChanges { + sessions: events, + keys: changes + .1 + .iter() + .map(|it| Key { + session_id: it.session_id.clone(), + room_id: it.room_id.to_string(), + }) + .collect(), + })) + }); + match result { + Ok(changes) => changes, + Err(error) => { + eprintln!("Failed to process receive_sync_changes: {:?}", error); + panic!(); //TODO + } + } } pub(crate) fn share_room_key( @@ -326,72 +410,87 @@ impl CryptoMachine { only_trusted: bool, visibility: u8, ) -> Vec { - use matrix_sdk_common::ruma::events::room::history_visibility::HistoryVisibility; - self.runtime - .block_on(async { - let room_id = RoomId::parse(room_id).unwrap(); - let user_ids: Vec = user_ids - .iter() - .map(|it| UserId::parse(it).unwrap()) - .collect(); - self.machine - .as_ref() - .unwrap() - .share_room_key( - &room_id, - user_ids.iter().map(Deref::deref), - EncryptionSettings { - history_visibility: match visibility { - 0 => HistoryVisibility::Invited, - 1 => HistoryVisibility::Joined, - 2 => HistoryVisibility::Shared, - 3 => HistoryVisibility::WorldReadable, - _ => panic!("Invalid value for visibility"), - }, - only_allow_trusted_devices: only_trusted, - .. Default::default() + let result: Result, Box> = self.runtime.block_on(async { + let room_id = RoomId::parse(room_id)?; + let user_ids: Vec = user_ids + .iter() + .filter_map(|it| UserId::parse(it).ok()) + .collect(); + Ok(self + .machine + .as_ref() + .ok_or("No crypto machine")? + .share_room_key( + &room_id, + user_ids.iter().map(Deref::deref), + EncryptionSettings { + history_visibility: match visibility { + 0 => HistoryVisibility::Invited, + 1 => HistoryVisibility::Joined, + 2 => HistoryVisibility::Shared, + 3 => HistoryVisibility::WorldReadable, + _ => panic!("Invalid value for visibility"), }, - ) - .await - .unwrap() //TODO settings? - }) - .iter() - .map(|it| ToDeviceRequest { - txn_id: it.txn_id.to_string(), - event_type: it.event_type.to_string(), - messages: it.messages.clone(), - }) - .collect() + only_allow_trusted_devices: only_trusted, + ..Default::default() + }, + ) + .await? + .iter() + .map(|it| ToDeviceRequest { + txn_id: it.txn_id.to_string(), + event_type: it.event_type.to_string(), + messages: it.messages.clone(), + }) + .collect()) + }); + match result { + Ok(requests) => requests, + Err(error) => { + eprintln!("Failed to share room key: {:?}", error); + //FIXME: Send error more explicitely? + return Default::default(); + } + } } //TODO lock pub(crate) fn get_missing_sessions(&mut self, user_ids: Vec) -> Box { - self.runtime.block_on(async { - let user_ids: Vec = user_ids + let result: Result, Box> = self.runtime.block_on(async { + let user_ids: Vec = user_ids .iter() - .map(|it| UserId::parse(it).unwrap()) + .filter_map(|it| UserId::parse(it).ok()) .collect(); - if let Some((id, request)) = self + + let maybe_request = self .machine .as_ref() - .unwrap() + .ok_or("No crypto machine")? .get_missing_sessions(user_ids.iter().map(Deref::deref)) - .await - .unwrap() - { - Box::new(KeysClaimRequest { + .await?; + + if let Some((id, request)) = maybe_request { + Ok(Box::new(KeysClaimRequest { id: id.to_string(), timeout: request.timeout, one_time_keys: request.one_time_keys, - }) + })) } else { - Box::new(KeysClaimRequest { + //TODO make this nicer + Ok(Box::new(KeysClaimRequest { id: "".to_string(), timeout: None, one_time_keys: Default::default(), - }) + })) } - }) + }); + match result { + Ok(request) => request, + Err(error) => { + eprintln!("Failed to process get_missing_sessions: {:?}", error); + panic!(); //TODO + } + } } pub(crate) fn encrypt_room_event( @@ -400,56 +499,71 @@ impl CryptoMachine { content: String, matrix_type: String, ) -> String { - self.runtime.block_on(async { - let room_id = RoomId::parse(room_id).unwrap(); - serde_json::to_string( - &self - .machine - .as_ref() - .unwrap() - //TODO: Don't hardcode this - .encrypt_room_event_raw( - &room_id, - &matrix_type, - &serde_json::from_str(content.as_str()).unwrap(), - ) - .await - .unwrap(), - ) - .unwrap() - }) + let result: Result> = self.runtime.block_on(async { + let room_id = RoomId::parse(room_id)?; + let encrypted = self + .machine + .as_ref() + .ok_or("No crypto machine")? + .encrypt_room_event_raw( + &room_id, + &matrix_type, + &serde_json::from_str(content.as_str())?, + ) + .await?; + + Ok(serde_json::to_string(&encrypted)?) + }); + match result { + Ok(event) => event, + Err(error) => { + eprintln!("Failed to process encrypt_room_event: {:?}", error); + Default::default() // TODO return error explicitely + } + } } pub(crate) fn decrypt_room_event(&mut self, room_id: String, json: String) -> String { - self.runtime.block_on(async { - let room_id = RoomId::parse(room_id).unwrap(); - if let Ok(event) = self + let result: Result> = self.runtime.block_on(async { + let room_id = RoomId::parse(room_id)?; + let event = self .machine .as_ref() - .unwrap() - .decrypt_room_event(&serde_json::from_str(json.as_str()).unwrap(), &room_id) - .await - { - serde_json::to_string(&event.event).unwrap() - } else { + .ok_or("No crypto machine")? + .decrypt_room_event(&{ serde_json::from_str(json.as_str())? }, &room_id) + .await?; + Ok(serde_json::to_string(&event.event)?) + }); + match result { + Ok(event) => event, + Err(error) => { + eprintln!("Failed to process decrypt_room_event: {:?}", error); + //TODO: return error Default::default() } - }) + } } pub(crate) fn update_tracked_users(&mut self, user_ids: Vec) { - self.runtime.block_on(async { + let result: Result<(), Box> = self.runtime.block_on(async { let user_ids: Vec = user_ids .iter() - .map(|it| UserId::parse(it).unwrap()) + .filter_map(|it| UserId::parse(it).ok()) .collect(); self.machine .as_ref() - .unwrap() + .ok_or("No crypto machine")? .update_tracked_users(user_ids.iter().map(Deref::deref)) - .await - .unwrap(); + .await?; + Ok(()) }); + match result { + Ok(_) => {} + Err(error) => { + eprintln!("Failed to process update_tracked_users: {:?}", error); + //FIXME: return error + } + } } pub(crate) fn accept_verification( @@ -457,17 +571,27 @@ impl CryptoMachine { remote_user: String, verification_id: String, ) -> Box { - let user_id = UserId::parse(remote_user).unwrap(); - //TODO: why is there sometimes no request? - Box::new(OutgoingKeyVerificationRequest( - self.machine - .as_ref() - .unwrap() - .get_verification_request(&user_id, &verification_id) - .unwrap() - .accept() - .unwrap(), - )) + let result: Result, Box> = + self.runtime.block_on(async { + let user_id = UserId::parse(remote_user.clone())?; + //TODO: why is there sometimes no request? + Ok(Box::new(OutgoingKeyVerificationRequest( + self.machine + .as_ref() + .expect("Crypto machine must be valid") + .get_verification_request(&user_id, &verification_id) + .ok_or("No session")? + .accept() + .ok_or("No request")?, + ))) + }); + match result { + Ok(request) => request, + Err(error) => { + eprintln!("Failed to process accept_verification: {:?}", error); + panic!(); //TODO: return error + } + } } pub(crate) fn confirm_verification( @@ -475,35 +599,43 @@ impl CryptoMachine { remote_user: String, verification_id: String, ) -> Box { - self.runtime.block_on(async { + let result: Result, Box> = self.runtime.block_on(async { //TODO signature upload - let user_id = UserId::parse(remote_user).unwrap(); - let VerificationRequestState::Transitioned { verification } = self + let user_id = UserId::parse(remote_user)?; + let (outgoing_verification_requests, signature_upload_request) = match self .machine .as_ref() - .unwrap() + .ok_or("No crypto machine")? .get_verification_request(&user_id, &verification_id) - .unwrap() + .ok_or("No verification session for {remote_user} {verification_id}")? .state() - else { - panic!(); - }; - let Verification::SasV1(sas) = verification else { - panic!(); - }; - let (outgoing_verification_requests, signature_upload_request) = - sas.confirm().await.unwrap(); + { + VerificationRequestState::Transitioned { + verification: Verification::SasV1(sas), + } => Some(sas), + _ => None, + } + .ok_or("Unexpected state")? + .confirm() + .await?; let outgoing_verification_requests = outgoing_verification_requests .iter() .map(|it| OutgoingKeyVerificationRequest(it.clone())) .collect(); - Box::new(ConfirmRequests { + Ok(Box::new(ConfirmRequests { verification: outgoing_verification_requests, signature: signature_upload_request, - }) - }) + })) + }); + match result { + Ok(result) => result, + Err(error) => { + eprintln!("Failed to process confirm_verification: {:?}", error); + panic!(); // TODO + } + } } pub(crate) fn start_sas( @@ -511,20 +643,27 @@ impl CryptoMachine { remote_user: String, verification_id: String, ) -> Box { - self.runtime.block_on(async { - let user_id = UserId::parse(&remote_user).unwrap(); - let result = self - .machine - .as_ref() - .unwrap() - .get_verification_request(&user_id, &verification_id) - .unwrap() - .start_sas() - .await - .unwrap() - .unwrap(); - Box::new(OutgoingKeyVerificationRequest(result.1)) - }) + let result: Result, Box> = + self.runtime.block_on(async { + let user_id = UserId::parse(&remote_user)?; + let result = self + .machine + .as_ref() + .ok_or("No crypto machine")? + .get_verification_request(&user_id, &verification_id) + .ok_or("No verification request")? + .start_sas() + .await? + .ok_or("No request")?; + Ok(Box::new(OutgoingKeyVerificationRequest(result.1))) + }); + match result { + Ok(request) => request, + Err(error) => { + eprintln!("Failed to process start_sas: {:?}", error); + panic!(); //TODO; + } + } } pub(crate) fn accept_sas( @@ -532,24 +671,34 @@ impl CryptoMachine { remote_user: String, verification_id: String, ) -> Box { - let user_id = UserId::parse(remote_user).unwrap(); - let Some(session) = self - .machine - .as_ref() - .unwrap() - .get_verification_request(&user_id, &verification_id) - else { - panic!() - }; - - if let VerificationRequestState::Transitioned { verification } = session.state() { - if let Verification::SasV1(sas) = verification { - Box::new(OutgoingKeyVerificationRequest(sas.accept().unwrap())) - } else { - panic!() + let result: Result, Box> = + self.runtime.block_on(async { + let user_id = UserId::parse(remote_user)?; + let session = self + .machine + .as_ref() + .ok_or("No crypto machine")? + .get_verification_request(&user_id, &verification_id) + .ok_or("No verification request")?; + + Ok(Box::new(OutgoingKeyVerificationRequest( + match session.state() { + VerificationRequestState::Transitioned { + verification: Verification::SasV1(sas), + } => Some(sas), + _ => None, + } + .ok_or("Invalid state")? + .accept() + .ok_or("No request")?, + ))) + }); + match result { + Ok(request) => request, + Err(error) => { + eprintln!("Failed to process accept_sas: {:?}", error); + panic!(); //TODO: return error instead } - } else { - panic!() } } @@ -558,98 +707,131 @@ impl CryptoMachine { remote_user: String, verification_id: String, ) -> u8 { - let user_id = UserId::parse(remote_user).unwrap(); - match self - .machine - .as_ref() - .expect("Should not happen") - .get_verification_request(&user_id, &verification_id) - { - Some(session) => match session.state() { - VerificationRequestState::Created { .. } => 0, - VerificationRequestState::Requested { .. } => 1, - VerificationRequestState::Ready { .. } => 2, - VerificationRequestState::Transitioned { .. } => 3, - VerificationRequestState::Done => 4, - VerificationRequestState::Cancelled(_) => 5, - }, - None => 6, + let result: Result> = self.runtime.block_on(async { + let user_id = UserId::parse(remote_user)?; + Ok( + match self + .machine + .as_ref() + .ok_or("No crypto machine")? + .get_verification_request(&user_id, &verification_id) + { + Some(session) => match session.state() { + VerificationRequestState::Created { .. } => 0, + VerificationRequestState::Requested { .. } => 1, + VerificationRequestState::Ready { .. } => 2, + VerificationRequestState::Transitioned { .. } => 3, + VerificationRequestState::Done => 4, + VerificationRequestState::Cancelled(_) => 5, + }, + None => 6, + }, + ) + }); + match result { + Ok(state) => state, + Err(error) => { + eprintln!("Failed to process verification_get_state: {:?}", error); + 6 //FIXME return errors explicitely + // TODO: use enum + } } } pub(crate) fn sas_get_state(&mut self, remote_user: String, verification_id: String) -> u8 { - let user_id = UserId::parse(remote_user).unwrap(); - let Some(session) = self - .machine - .as_ref() - .unwrap() - .get_verification_request(&user_id, &verification_id) - else { - return 6; - }; - - if let VerificationRequestState::Transitioned { verification } = session.state() { - if let Verification::SasV1(sas) = verification { - match sas.state() { - SasState::Started { .. } => 0, - SasState::Accepted { .. } => 1, - SasState::KeysExchanged { .. } => 2, - SasState::Confirmed => 3, - SasState::Done { .. } => 4, - SasState::Cancelled(_) => 5, - } - } else { - panic!() + let result: Result> = self.runtime.block_on(async { + let user_id = UserId::parse(remote_user)?; + let session = self + .machine + .as_ref() + .ok_or("No crypto machine")? + .get_verification_request(&user_id, &verification_id) + .ok_or("No session")?; + + Ok( + if let VerificationRequestState::Transitioned { + verification: Verification::SasV1(sas), + } = session.state() + { + match sas.state() { + SasState::Started { .. } => 0, + SasState::Accepted { .. } => 1, + SasState::KeysExchanged { .. } => 2, + SasState::Confirmed => 3, + SasState::Done { .. } => 4, + SasState::Cancelled(_) => 5, + } + } else { + // this is (in the current setup) mostly normal, since we're always querying sas state. + 6 + }, + ) + }); + match result { + Ok(state) => state, + Err(error) => { + eprintln!("Failed to process sas_get_state: {:?}", error); + 6 // FIXME: return errors explicitely + // TODO: use enum } - } else { - // this is (in the current setup) mostly normal, since we're always querying sas state. - 6 } } pub(crate) fn sas_emoji(&self, remote_user: String, verification_id: String) -> Vec { - let user_id = UserId::parse(remote_user).unwrap(); - if let VerificationRequestState::Transitioned { verification } = self - .machine - .as_ref() - .unwrap() - .get_verification_request(&user_id, &verification_id) - .unwrap() - .state() - { - if let Verification::SasV1(sas) = verification { - sas.emoji() - .expect("Emoji can't be presented yet") - .iter() - .map(|e| crate::verification::Emoji(e.clone())) - .collect() - } else { - panic!() + let result: Result, Box> = self.runtime.block_on(async { + Ok(match self + .machine + .as_ref() + .ok_or("No crypto machine")? + .get_verification_request(&UserId::parse(&remote_user)?, &verification_id) + .ok_or("No request")? + .state() + { + VerificationRequestState::Transitioned { + verification: Verification::SasV1(sas), + } => Some(sas), + _ => None, + } + .ok_or("Session in invalid state")? + .emoji() + .expect("Emoji can't be presented yet") + .iter() + .map(|e| crate::verification::Emoji(e.clone())) + .collect()) + }); + match result { + Ok(emoji) => emoji, + Err(error) => { + eprintln!("Failed to process sas_emoji: {:?}", error); + panic!() //TODO } - } else { - panic!() } } - pub(crate) fn request_device_verification( &mut self, user_id: String, device_id: String, ) -> Box { - self.runtime.block_on(async { - let user_id = UserId::parse(user_id).unwrap(); + let result: Result, Box> = self.runtime.block_on(async { + let user_id = UserId::parse(user_id)?; let device_id: Box = device_id.into(); let device = self .machine .as_ref() - .unwrap() + .ok_or("No crypto machine")? .get_device(&user_id, &device_id, None) - .await - .unwrap() - .unwrap(); + .await? + .ok_or("No request")?; let (session, outgoing) = device.request_verification().await; - Box::new(CreatedSession(session, outgoing)) - }) + Ok(Box::new(CreatedSession(session, outgoing))) + }); + match result { + Ok(session) => session, + Err(error) => { + eprintln!("Failed to process request_device_verification: {:?}", error); + panic!(); //TODO + } + } } pub(crate) fn request_user_verification( @@ -658,48 +840,67 @@ impl CryptoMachine { room_id: String, request_event_id: String, ) -> Box { - self.runtime.block_on(async { - let user_id = UserId::parse(user_id).unwrap(); - let room_id = RoomId::parse(room_id).unwrap(); - let event_id = EventId::parse(request_event_id).unwrap(); - let identity = self - .machine - .as_ref() - .unwrap() - .get_identity(&user_id, None) - .await - .unwrap() - .unwrap(); - if let UserIdentities::Other(other) = identity { - Box::new(KeyVerificationRequest( + let result: Result, Box> = + self.runtime.block_on(async { + let user_id = UserId::parse(user_id)?; + let room_id = RoomId::parse(room_id)?; + let event_id = EventId::parse(request_event_id)?; + let identity = self + .machine + .as_ref() + .expect("Crypto machine must be valid") + .get_identity(&user_id, None) + .await? + .ok_or("No request")?; + let other = match identity { + UserIdentities::Other(other) => Some(other), + _ => None, + } + .ok_or("Not a different user")?; + Ok(Box::new(KeyVerificationRequest( other .request_verification(&room_id, &event_id, None /*TODO?*/) .await, - )) - } else { - panic!() + ))) + }); + match result { + Ok(request) => request, + Err(error) => { + eprintln!("Failed to process request_user_verification: {:?}", error); + panic!(); //TODO } - }) + } } pub(crate) fn request_user_verification_content(&mut self, user_id: String) -> String { - self.runtime.block_on(async { - let user_id = UserId::parse(user_id).unwrap(); - let identity = self + let result: Result> = self.runtime.block_on(async { + let user_id = UserId::parse(user_id).expect("User id must be valid"); + let other = match self .machine .as_ref() - .unwrap() + .ok_or("No crypto machine")? .get_identity(&user_id, None) - .await - .unwrap() - .unwrap(); - if let UserIdentities::Other(other) = identity { - serde_json::to_string(&other.verification_request_content(None /*TODO ?*/).await) - .unwrap() - } else { - Default::default() + .await? + .ok_or("User not found")? + { + UserIdentities::Other(other) => Some(other), + _ => None, } - }) + .ok_or("Not for a remote user")?; + Ok(serde_json::to_string( + &other.verification_request_content(None /*TODO ?*/).await, + )?) + }); + match result { + Ok(request) => request, + Err(error) => { + eprintln!( + "Failed to process request_user_verification_content: {:?}", + error + ); + Default::default() // FIXME: error explicitely + } + } } pub(crate) fn get_room_event_encryption_info( @@ -707,35 +908,51 @@ impl CryptoMachine { event: String, room_id: String, ) -> Box { - self.runtime.block_on(async { - let room_id = RoomId::parse(room_id).unwrap(); + let result: Result, Box> = self.runtime.block_on(async { + let room_id = RoomId::parse(room_id)?; let info = self .machine .as_ref() - .unwrap() - .get_room_event_encryption_info(&serde_json::from_str(&event).unwrap(), &room_id) - .await - .unwrap(); - Box::new(EncryptionInfo(info)) - }) + .ok_or("No crypto machine")? + .get_room_event_encryption_info(&serde_json::from_str(&event)?, &room_id) + .await?; + Ok(Box::new(EncryptionInfo(info))) + }); + match result { + Ok(encryption_info) => encryption_info, + Err(error) => { + eprintln!( + "Failed to process get_room_event_encryption_info: {:?}", + error + ); + panic!(); //TODO + } + } } pub(crate) fn receive_verification_event(&mut self, full_json: String) { - self.runtime.block_on(async { + let result: Result<(), Box> = self.runtime.block_on(async { self.machine .as_ref() - .unwrap() - .receive_verification_event(&serde_json::from_str(&full_json).unwrap()) - .await - .unwrap(); - }) + .ok_or("No crypto machine")? + .receive_verification_event(&serde_json::from_str(&full_json)?) + .await?; + Ok(()) + }); + match result { + Ok(_) => {} + Err(error) => { + eprintln!("Failed to process receive_verification_event: {:?}", error); + // FIXME: explicitely error + } + } } pub(crate) fn load_secrets( &mut self, passphrase: String, key_id: String, - iterations: u32, + iterations: u64, salt: String, iv: String, mac: String, @@ -751,309 +968,396 @@ impl CryptoMachine { user_key_iv: String, user_key_cipher: String, user_key_mac: String, - version: String + version: String, ) -> String { - self.runtime.block_on(async { + let result: Result> = self.runtime.block_on(async { + use matrix_sdk_common::ruma::serde::Base64; let mut content = SecretStorageKeyEventContent::new( key_id, SecretStorageEncryptionAlgorithm::V1AesHmacSha2( SecretStorageV1AesHmacSha2Properties::new( - matrix_sdk_common::ruma::serde::Base64::parse(iv).unwrap(), - matrix_sdk_common::ruma::serde::Base64::parse(mac).unwrap(), + Base64::parse(iv)?, + Base64::parse(mac)?, ), ), ); - content.passphrase = Some(PassPhrase::new(salt, UInt::new_wrapping(iterations as u64))); - - let ss_key = SecretStorageKey::from_account_data(&passphrase, content).unwrap(); - let Ok(decryption_key) = BackupDecryptionKey::from_base64( - &String::from_utf8( - ss_key - .decrypt( - &AesHmacSha2EncryptedData { - iv: matrix_sdk_common::ruma::serde::Base64::::parse( - backup_key_iv, - ) - .unwrap() - .as_bytes() - .try_into() - .unwrap(), - ciphertext: matrix_sdk_common::ruma::serde::Base64::parse( - backup_key_cipher, - ) - .unwrap(), - mac: matrix_sdk_common::ruma::serde::Base64::::parse( - backup_key_mac, - ) - .unwrap() - .as_bytes() - .try_into() - .unwrap(), - }, - &SecretName::RecoveryKey, - ) - .unwrap(), - ) - .unwrap(), - ) else { - return Default::default(); + content.passphrase = Some(PassPhrase::new(salt, UInt::new_wrapping(iterations))); + + let ss_key = match SecretStorageKey::from_account_data(&passphrase, content) { + Ok(key) => key, + Err(error) => { + eprintln!( + "Failed to load secret storage key from account data: {:?}", + error + ); + //TODO: signal that the passphrase is wrong + //TODO: don't panic :) + panic!() + } }; + let backup_decryption_key = ss_key.decrypt( + &AesHmacSha2EncryptedData { + iv: Base64::::parse(backup_key_iv)? + .as_bytes() + .try_into()?, + ciphertext: Base64::parse(backup_key_cipher)?, + mac: Base64::::parse(backup_key_mac)? + .as_bytes() + .try_into()?, + }, + &SecretName::RecoveryKey, + ); + + let backup_decryption_key = + BackupDecryptionKey::from_base64(&String::from_utf8(backup_decryption_key?)?)?; + let master_private = String::from_utf8( - ss_key - .decrypt( - &AesHmacSha2EncryptedData { - iv: matrix_sdk_common::ruma::serde::Base64::::parse( - master_key_iv, - ) - .unwrap() + ss_key.decrypt( + &AesHmacSha2EncryptedData { + iv: Base64::::parse(master_key_iv)? .as_bytes() - .try_into() - .unwrap(), - ciphertext: matrix_sdk_common::ruma::serde::Base64::parse( - master_key_cipher, - ) - .unwrap(), - mac: matrix_sdk_common::ruma::serde::Base64::::parse( - master_key_mac, - ) - .unwrap() + .try_into()?, + ciphertext: Base64::::parse(master_key_cipher)?, + mac: Base64::::parse(master_key_mac)? .as_bytes() - .try_into() - .unwrap(), - }, - &SecretName::CrossSigningMasterKey, - ) - .unwrap(), - ) - .unwrap(); + .try_into()?, + }, + &SecretName::CrossSigningMasterKey, + )?, + )?; let self_private = String::from_utf8( - ss_key - .decrypt( - &AesHmacSha2EncryptedData { - iv: matrix_sdk_common::ruma::serde::Base64::::parse( - self_key_iv, - ) - .unwrap() + ss_key.decrypt( + &AesHmacSha2EncryptedData { + iv: matrix_sdk_common::ruma::serde::Base64::::parse(self_key_iv)? .as_bytes() - .try_into() - .unwrap(), - ciphertext: matrix_sdk_common::ruma::serde::Base64::parse( - self_key_cipher, - ) - .unwrap(), - mac: matrix_sdk_common::ruma::serde::Base64::::parse( - self_key_mac, - ) - .unwrap() - .as_bytes() - .try_into() - .unwrap(), - }, - &SecretName::CrossSigningSelfSigningKey, - ) - .unwrap(), - ) - .unwrap(); + .try_into()?, + ciphertext: matrix_sdk_common::ruma::serde::Base64::parse(self_key_cipher)?, + mac: matrix_sdk_common::ruma::serde::Base64::::parse( + self_key_mac, + )? + .as_bytes() + .try_into()?, + }, + &SecretName::CrossSigningSelfSigningKey, + )?, + )?; let user_private = String::from_utf8( - ss_key - .decrypt( - &AesHmacSha2EncryptedData { - iv: matrix_sdk_common::ruma::serde::Base64::::parse( - user_key_iv, - ) - .unwrap() - .as_bytes() - .try_into() - .unwrap(), - ciphertext: matrix_sdk_common::ruma::serde::Base64::parse( - user_key_cipher, - ) - .unwrap(), - mac: matrix_sdk_common::ruma::serde::Base64::::parse( - user_key_mac, - ) - .unwrap() + ss_key.decrypt( + &AesHmacSha2EncryptedData { + iv: matrix_sdk_common::ruma::serde::Base64::::parse(user_key_iv)? .as_bytes() - .try_into() - .unwrap(), - }, - &SecretName::CrossSigningUserSigningKey, - ) - .unwrap(), - ) - .unwrap(); + .try_into()?, + ciphertext: matrix_sdk_common::ruma::serde::Base64::parse(user_key_cipher)?, + mac: matrix_sdk_common::ruma::serde::Base64::::parse( + user_key_mac, + )? + .as_bytes() + .try_into()?, + }, + &SecretName::CrossSigningUserSigningKey, + )?, + )?; self.machine .as_ref() - .unwrap() + .ok_or("No crypto machine")? .backup_machine() - .save_decryption_key(Some(decryption_key.clone()), Some(version.clone())) - .await - .unwrap(); + .save_decryption_key(Some(backup_decryption_key.clone()), Some(version.clone())) + .await?; - let backup_key = decryption_key.megolm_v1_public_key(); + let backup_key = backup_decryption_key.megolm_v1_public_key(); backup_key.set_version(version); - self.machine.as_ref().unwrap().backup_machine().enable_backup_v1(backup_key).await.unwrap(); + self.machine + .as_ref() + .ok_or("No crypto machine")? + .backup_machine() + .enable_backup_v1(backup_key) + .await?; self.machine .as_ref() - .unwrap() + .ok_or("No crypto machine")? .import_cross_signing_keys(CrossSigningKeyExport { master_key: Some(master_private), self_signing_key: Some(self_private), user_signing_key: Some(user_private), }) - .await - .unwrap(); + .await?; - let machine = self.machine.as_ref().unwrap(); + let machine = self.machine.as_ref().ok_or("No crypto machine")?; let upload_request = if let Some(own_device) = machine .get_device(machine.user_id(), machine.device_id(), None) - .await - .unwrap() + .await? { - Some(own_device.verify().await.unwrap()) + Some(own_device.verify().await?) } else { None }; - serde_json::to_string(&upload_request.unwrap().signed_keys).unwrap() - }) + //TODO: we probably don't want to abort/return entirely if this request doesn't exist? also, check the rest of the code for that problem + // what should be done if there is no upload request? + Ok(serde_json::to_string( + &upload_request.ok_or("No upload request")?.signed_keys, + )?) + }); + match result { + Ok(upload_request) => upload_request, + Err(error) => { + eprintln!("Error while loading secrets: {:?}", error); + //TODO send proper error to caller + Default::default() + } + } } pub(crate) fn import_from_backup(&mut self, response: String) { - self.runtime.block_on(async { + let result: Result<(), Box> = self.runtime.block_on(async { #[derive(Deserialize)] struct Response { rooms: BTreeMap, } - let backed_up_keys: Response = serde_json::from_str(&response).unwrap(); + let backed_up_keys = serde_json::from_str::(&response)?; let mut decrypted_room_keys: BTreeMap<_, BTreeMap<_, _>> = BTreeMap::new(); - let decryption_key = self.machine.as_ref().unwrap().backup_machine().get_backup_keys().await.unwrap().decryption_key; - - if let Some(decryption_key) = decryption_key { - for (room_id, room_keys) in backed_up_keys.rooms { - for (session_id, room_key) in room_keys.sessions { - let Ok(room_key) = room_key.deserialize() else { - continue; - }; - - let Ok(room_key) = decryption_key.decrypt_session_data(room_key.session_data) - else { - continue; - }; - - decrypted_room_keys - .entry(room_id.to_owned()) - .or_default() - .insert(session_id, room_key); - } + let decryption_key = self + .machine + .as_ref() + .ok_or("No crypto machine")? + .backup_machine() + .get_backup_keys() + .await? + .decryption_key + .ok_or("No backup key")?; + + for (room_id, room_keys) in backed_up_keys.rooms { + for (session_id, room_key) in room_keys.sessions { + let Ok(room_key) = room_key.deserialize() else { + continue; + }; + //TODO: check all ? operators whether they change control flow in the way I intended + let Ok(room_key) = decryption_key.decrypt_session_data(room_key.session_data) + else { + continue; + }; + + decrypted_room_keys + .entry(room_id.to_owned()) + .or_default() + .insert(session_id, room_key); } + } - self.machine - .as_ref() - .unwrap() - .backup_machine() - .import_backed_up_room_keys(decrypted_room_keys, |_, _| {}) - .await - .unwrap(); + self.machine + .as_ref() + .ok_or("No crypto machine")? + .backup_machine() + .import_backed_up_room_keys(decrypted_room_keys, |_, _| {}) + .await?; + Ok(()) + }); + match result { + Ok(_) => {} + Err(error) => { + eprintln!("Failed to process import_from_backup: {:?}", error); + panic!(); //FIXME: return error } - }) + } } pub(crate) fn request_self_verification(&mut self) -> Box { - self.runtime.block_on(async { - if let UserIdentities::Own(own) = self + let result: Result, Box> = self.runtime.block_on(async { + let own = match self .machine .as_ref() - .unwrap() - .get_identity(self.machine.as_ref().unwrap().user_id(), None) - .await - .unwrap() - .unwrap() + .ok_or("No crypto machine")? + .get_identity( + self.machine.as_ref().ok_or("No crypto machine")?.user_id(), + None, + ) + .await? + .ok_or("Own identity not found")? { - let session = own.request_verification().await.unwrap(); - Box::new(CreatedSession(session.0, session.1)) - } else { - panic!() + UserIdentities::Own(own) => Some(own), + _ => None, } - }) + .ok_or("Identity not found")?; + + let session = own.request_verification().await?; + Ok(Box::new(CreatedSession(session.0, session.1))) + }); + match result { + Ok(session) => session, + Err(error) => { + eprintln!("Failed to process request_self_verification: {:?}", error); + panic!(); //TODO + } + } } pub(crate) fn request_secrets_from_devices(&mut self) { - self.runtime.block_on(async { + let result: Result<(), Box> = self.runtime.block_on(async { self.machine .as_ref() - .unwrap() + .ok_or("No crypto machine")? .query_missing_secrets_from_other_sessions() - .await - .unwrap(); + .await?; + Ok(()) }); + match result { + Ok(_) => {} + Err(error) => { + eprintln!( + "Failed to process request_secrets_from_devices: {:?}", + error + ); + // FIXME: explicitely error + } + } } pub(crate) fn has_pending_backup_key(&self) -> bool { - self.runtime.block_on(async { - let machine = self.machine.as_ref().unwrap(); - machine.store().get_secrets_from_inbox(&SecretName::RecoveryKey).await.unwrap().len() > 0 && machine.backup_machine().get_backup_keys().await.unwrap().decryption_key.is_none() - }) + let result: Result> = self.runtime.block_on(async { + let machine = self.machine.as_ref().ok_or("No crypto machine")?; + Ok(machine + .store() + .get_secrets_from_inbox(&SecretName::RecoveryKey) + .await? + .len() + > 0 + && machine + .backup_machine() + .get_backup_keys() + .await? + .decryption_key + .is_none()) + }); + match result { + Ok(has) => has, + Err(error) => { + eprintln!("Failed to process has_pending_backup_key: {:?}", error); + panic!(); //TODO + } + } } pub(crate) fn initialize_existing_backup(&mut self, version_reply: String) { - self.runtime.block_on(async { - let machine = self.machine.as_ref().unwrap(); - for secret in machine.store().get_secrets_from_inbox(&SecretName::RecoveryKey).await.unwrap() { - let decryption_key = BackupDecryptionKey::from_base64(&secret.event.content.secret).unwrap(); + let result: Result<(), Box> = self.runtime.block_on(async { + let machine = self.machine.as_ref().expect("Crypto machine must be valid"); + for secret in machine + .store() + .get_secrets_from_inbox(&SecretName::RecoveryKey) + .await? + { + let decryption_key = + BackupDecryptionKey::from_base64(&secret.event.content.secret)?; #[derive(Deserialize)] struct Response { version: String, } - let current_version: Response = serde_json::from_str(&version_reply).unwrap(); - let backup_info: RoomKeyBackupInfo = serde_json::from_str::(&version_reply).unwrap().into(); + let current_version: Response = serde_json::from_str(&version_reply)?; + let backup_info: RoomKeyBackupInfo = + serde_json::from_str::(&version_reply)?.into(); if decryption_key.backup_key_matches(&backup_info) { - machine.backup_machine().disable_backup().await.unwrap(); + machine.backup_machine().disable_backup().await?; let backup_key = decryption_key.megolm_v1_public_key(); backup_key.set_version(current_version.version.clone()); - machine.backup_machine().save_decryption_key(Some(decryption_key.to_owned()), Some(current_version.version)).await.unwrap(); - machine.backup_machine().enable_backup_v1(backup_key).await.unwrap(); + machine + .backup_machine() + .save_decryption_key( + Some(decryption_key.to_owned()), + Some(current_version.version), + ) + .await?; + machine + .backup_machine() + .enable_backup_v1(backup_key) + .await?; break; } } - machine.store().delete_secrets_from_inbox(&SecretName::RecoveryKey).await.unwrap(); - }) + machine + .store() + .delete_secrets_from_inbox(&SecretName::RecoveryKey) + .await?; + Ok(()) + }); + match result { + Ok(_) => {} + Err(error) => { + eprintln!("Failed to process initialize_existing_backup: {:?}", error); + //TODO: return an error + } + } } pub(crate) fn backup_keys(&mut self) -> Box { - self.runtime.block_on(async { - let backup_request = self.machine.as_ref().unwrap().backup_machine().backup().await.unwrap(); + let result: Result, Box> = self.runtime.block_on(async { + let backup_request = self + .machine + .as_ref() + .ok_or("No crypto machine")? + .backup_machine() + .backup() + .await?; if let Some(backup_request) = backup_request { - Box::new(BackupRequest { + Ok(Box::new(BackupRequest { transaction_id: Some(backup_request.0), request: Some(backup_request.1), - }) + })) } else { - Box::new(BackupRequest { + //TODO improve + Ok(Box::new(BackupRequest { transaction_id: None, request: None, - }) + })) } - }) + }); + match result { + Ok(result) => result, + Err(error) => { + eprintln!("Failed to process backup_keys: {:?}", error); + panic!(); //TODO + } + } } pub(crate) fn export_keys(&self, passphrase: String) -> String { use matrix_sdk_crypto::encrypt_room_key_export; - self.runtime.block_on(async { - encrypt_room_key_export(&self.machine.as_ref().unwrap().export_room_keys(|_| true).await.unwrap(), &passphrase, 500_000).unwrap() - }) + let result: Result> = self.runtime.block_on(async { + Ok(encrypt_room_key_export( + &self + .machine + .as_ref() + .ok_or("No crypto machine")? + .export_room_keys(|_| true) + .await?, + &passphrase, + 500_000, + )?) + }); + match result { + Ok(data) => data, + Err(error) => { + eprintln!("Failed to process export_keys: {:?}", error); + Default::default() //TODO explicitely return error + } + } } pub(crate) fn import_keys(&self, passphrase: String, ciphertext: String) -> Vec { use matrix_sdk_crypto::decrypt_room_key_export; - self.runtime.block_on(async { - let keys = decrypt_room_key_export(ciphertext.as_bytes(), &passphrase).unwrap(); - let keys = self.machine.as_ref().unwrap().store().import_exported_room_keys(keys, |_, _| {}).await.unwrap().keys; - let mut keys_vec = vec!(); + let result: Result, Box> = self.runtime.block_on(async { + let keys = decrypt_room_key_export(ciphertext.as_bytes(), &passphrase)?; + let keys = self + .machine + .as_ref() + .ok_or("No crypto machine")? + .store() + .import_exported_room_keys(keys, |_, _| {}) + .await? + .keys; + let mut keys_vec = vec![]; for room_id in keys.keys() { for sender in keys[room_id].keys() { for session_id in &keys[room_id][sender] { @@ -1064,27 +1368,61 @@ impl CryptoMachine { } } } - keys_vec - }) + Ok(keys_vec) + }); + match result { + Ok(keys) => keys, + Err(error) => { + eprintln!("Failed to process import_keys: {:?}", error); + panic!(); //TODO + } + } } pub(crate) fn all_sessions_verified(&self, user_id: String) -> bool { - self.runtime.block_on(async { - let user_id = UserId::parse(user_id).unwrap(); - let devices = self.machine.as_ref().unwrap().get_user_devices(&user_id, None).await.unwrap(); - let all_verified = devices.devices().all(|it| it.is_cross_signed_by_owner()); - all_verified - }) + let result: Result> = self.runtime.block_on(async { + let user_id = UserId::parse(user_id)?; + Ok(self + .machine + .as_ref() + .expect("Crypto machine must be valid") + .get_user_devices(&user_id, None) + .await + .unwrap() + .devices() + .all(|it| it.is_cross_signed_by_owner())) + }); + match result { + Ok(verified) => verified, + Err(error) => { + eprintln!("Failed to process all_sessions_verified: {:?}", error); + false // FIXME: explicitely error + } + } } pub(crate) fn is_user_verified(&self, user_id: String) -> bool { - self.runtime.block_on(async { - let user_id = UserId::parse(user_id).unwrap(); - let identity = self.machine.as_ref().unwrap().get_identity(&user_id, None).await.unwrap().unwrap(); - match identity { - UserIdentities::Own(own) => own.is_verified(), - UserIdentities::Other(other) => other.is_verified(), + let result: Result> = self.runtime.block_on(async { + Ok( + match self + .machine + .as_ref() + .ok_or("No crypto machine")? + .get_identity(&UserId::parse(&user_id)?, None) + .await? + .ok_or("Identity not found")? + { + UserIdentities::Own(own) => own.is_verified(), + UserIdentities::Other(other) => other.is_verified(), + }, + ) + }); + match result { + Ok(verified) => verified, + Err(error) => { + eprintln!("Failed to process is_user_verified: {:?}", error); + false //FIXME error explicitely } - }) + } } } @@ -1095,13 +1433,26 @@ pub(crate) struct BackupRequest { impl BackupRequest { pub(crate) fn transaction_id(&self) -> String { - self.transaction_id.clone().unwrap().to_string() + self.transaction_id + .clone() + .expect("BackupRequest::transaction_id must not be called when has_request is false") + .to_string() } pub(crate) fn version(&self) -> String { - self.request.clone().unwrap().version + self.request + .clone() + .expect("BackupRequest::version must not be called when has_request is false") + .version } pub(crate) fn rooms(&self) -> String { - serde_json::to_string(&self.request.clone().unwrap().rooms).unwrap() + serde_json::to_string( + &self + .request + .clone() + .expect("BackupRequest::rooms must not be called when has_request is false") + .rooms, + ) + .expect("Serde to_string should not fail") } pub(crate) fn has_request(&self) -> bool { self.request.is_some() diff --git a/Quotient/crypto-sdk/src/lib.rs b/Quotient/crypto-sdk/src/lib.rs index a548535c3..14887110e 100644 --- a/Quotient/crypto-sdk/src/lib.rs +++ b/Quotient/crypto-sdk/src/lib.rs @@ -1,9 +1,9 @@ +#![feature(unsafe_attributes)] + use std::{mem::ManuallyDrop, path::Path}; use matrix_sdk_common::ruma::{DeviceId, UserId}; - use matrix_sdk_crypto::OlmMachine; - use matrix_sdk_sqlite::SqliteCryptoStore; mod cryptomachine; @@ -12,9 +12,10 @@ mod file_crypto; mod request; mod verification; +use crate::ffi::InitError; use cryptomachine::CryptoMachine; -use cryptomachine::SyncChanges; use cryptomachine::Key; +use cryptomachine::SyncChanges; fn init( user_id: String, @@ -24,34 +25,61 @@ fn init( ) -> Box { // This will fail when initializing a second account, but that's ok let _ = tracing_subscriber::fmt().try_init(); - let rt = tokio::runtime::Runtime::new().unwrap(); + let rt = tokio::runtime::Runtime::new().expect("Failed to create runtime"); let _ = rt.enter(); - let machine = rt.block_on(async { - let user_id = UserId::parse(user_id).unwrap(); - let device_id: Box = device_id.into(); - - let store = SqliteCryptoStore::open(Path::new(&path), Some(&pickle_key)) - .await - .unwrap(); - - let machine = OlmMachine::with_store(&user_id, &device_id, store) - .await - .expect("Failed to load crypto database"); - - let state = machine.cross_signing_status().await; + enum InnerInitResult { + Err(InitError, String), + Ok(OlmMachine), + } - println!("Has: {} {} {} {}", state.has_master, state.has_self_signing, state.has_user_signing, machine.backup_machine().get_backup_keys().await.unwrap().decryption_key.is_some()); + let result = rt.block_on(async { + let user_id = match UserId::parse(&user_id) { + Ok(user_id) => user_id, + Err(err) => { + eprintln!( + "Failed to initialize cryptomachine: Invalid user id '{}'", + user_id + ); + return InnerInitResult::Err(InitError::InvalidUserId, err.to_string()); + } + }; + let device_id: Box = device_id.into(); - machine + let store = match SqliteCryptoStore::open(Path::new(&path), Some(&pickle_key)).await { + Ok(store) => store, + Err(err) => { + eprintln!("Failed to open crypto store: {:?}", err); + return InnerInitResult::Err(InitError::StoreFailed, err.to_string()); + } + }; + + match OlmMachine::with_store(&user_id, &device_id, store).await { + Ok(machine) => InnerInitResult::Ok(machine), + Err(err) => { + eprintln!("Failed to create olm machine: {:?}", err); + return InnerInitResult::Err(InitError::DatabaseError, err.to_string()); + } + } }); - Box::new(CryptoMachine { - runtime: rt, - machine: Some(ManuallyDrop::new(machine)), + Box::new(match result { + InnerInitResult::Ok(machine) => CryptoMachine { + error: InitError::Ok, + error_string: "Success".to_string(), + runtime: rt, + machine: Some(ManuallyDrop::new(machine)), + }, + InnerInitResult::Err(error, error_string) => CryptoMachine { + error, + error_string, + runtime: rt, + machine: None, + }, }) } +use cryptomachine::BackupRequest; use encryption_info::EncryptionInfo; use file_crypto::decrypt_file; use file_crypto::encrypt_file; @@ -61,10 +89,17 @@ use request::{ OutgoingRequest, ToDeviceRequest, }; use verification::{CreatedSession, Emoji}; -use cryptomachine::BackupRequest; #[cxx::bridge] mod ffi { + #[derive(Clone, PartialEq)] + enum InitError { + Ok, + InvalidUserId, + StoreFailed, + DatabaseError, + } + #[namespace = "crypto"] extern "Rust" { type CryptoMachine; @@ -90,10 +125,7 @@ mod ffi { pickle_key: String, ) -> Box; fn outgoing_requests(self: &CryptoMachine) -> Vec; - fn receive_sync_changes( - self: &mut CryptoMachine, - sync_json: String, - ) -> Box; + fn receive_sync_changes(self: &mut CryptoMachine, sync_json: String) -> Box; fn receive_verification_event(self: &mut CryptoMachine, full_json: String); // Mark requests as sent @@ -242,16 +274,13 @@ mod ffi { fn decrypt_file(bytes: &mut [u8], iv: String, key: String, hash: String) -> Vec; - fn import_from_backup( - self: &mut CryptoMachine, - response: String, - ); + fn import_from_backup(self: &mut CryptoMachine, response: String); fn load_secrets( self: &mut CryptoMachine, passphrase: String, key_id: String, - iterations: u32, + iterations: u64, salt: String, iv: String, mac: String, @@ -267,7 +296,7 @@ mod ffi { user_key_iv: String, user_key_cipher: String, user_key_mac: String, - version: String + version: String, ) -> String; fn verification_requests(self: &ConfirmRequests) -> Vec; @@ -296,5 +325,8 @@ mod ffi { fn all_sessions_verified(self: &CryptoMachine, user_id: String) -> bool; fn is_user_verified(self: &CryptoMachine, user_id: String) -> bool; + fn is_ok(self: &CryptoMachine) -> bool; + fn error(self: &CryptoMachine) -> InitError; + fn error_string(self: &CryptoMachine) -> String; } } diff --git a/Quotient/crypto-sdk/src/verification.rs b/Quotient/crypto-sdk/src/verification.rs index a6fd8c38a..c124673b0 100644 --- a/Quotient/crypto-sdk/src/verification.rs +++ b/Quotient/crypto-sdk/src/verification.rs @@ -13,7 +13,7 @@ impl CreatedSession { if let OutgoingVerificationRequest::ToDevice(request) = &self.1 { request.event_type.to_string() } else { - Default::default() + panic!("This is not a to-device event") } } @@ -21,15 +21,15 @@ impl CreatedSession { if let OutgoingVerificationRequest::ToDevice(request) = &self.1 { request.txn_id.to_string() } else { - Default::default() + panic!("This is not a to-device event") } } pub(crate) fn to_device_messages(&self) -> String { if let OutgoingVerificationRequest::ToDevice(request) = &self.1 { - serde_json::to_string(&request.messages).unwrap() + serde_json::to_string(&request.messages).expect("serde to_string should not fail") } else { - Default::default() + panic!("This is not a to-device event") } } }