matrix_sdk_crypto_ffi/machine.rs
1use std::{
2 collections::{BTreeMap, HashMap},
3 io::Cursor,
4 mem::ManuallyDrop,
5 ops::Deref,
6 sync::Arc,
7 time::Duration,
8};
9
10use js_int::UInt;
11use matrix_sdk_common::deserialized_responses::AlgorithmInfo;
12use matrix_sdk_crypto::{
13 CollectStrategy, DecryptionSettings, LocalTrust, OlmMachine as InnerMachine, OlmMachineBuilder,
14 UserIdentity as SdkUserIdentity,
15 backups::{
16 MegolmV1BackupKey as RustBackupKey, SignatureState,
17 SignatureVerification as RustSignatureCheckResult,
18 },
19 decrypt_room_key_export, encrypt_room_key_export,
20 olm::ExportedRoomKey,
21 store::types::{BackupDecryptionKey, Changes},
22 types::requests::ToDeviceRequest,
23};
24use ruma::{
25 DeviceKeyAlgorithm, EventId, OneTimeKeyAlgorithm, OwnedTransactionId, OwnedUserId, RoomId,
26 UserId,
27 api::{
28 IncomingResponseExt as _,
29 client::{
30 backup::add_backup_keys::v3::Response as KeysBackupResponse,
31 keys::{
32 claim_keys::v3::Response as KeysClaimResponse,
33 get_keys::v3::Response as KeysQueryResponse,
34 upload_keys::v3::Response as KeysUploadResponse,
35 upload_signatures::v3::Response as SignatureUploadResponse,
36 },
37 message::send_message_event::v3::Response as RoomMessageResponse,
38 sync::sync_events::{DeviceLists as RumaDeviceLists, v3::ToDevice},
39 to_device::send_event_to_device::v3::Response as ToDeviceResponse,
40 },
41 },
42 events::{
43 AnyMessageLikeEvent, AnySyncMessageLikeEvent, AnyTimelineEvent, MessageLikeEvent,
44 key::verification::VerificationMethod, room::message::MessageType,
45 },
46 serde::Raw,
47 to_device::DeviceIdOrAllDevices,
48};
49use serde::{Deserialize, Serialize};
50use serde_json::{Value, value::RawValue};
51use tokio::runtime::Runtime;
52use zeroize::Zeroize;
53
54use crate::{
55 BackupKeys, BackupRecoveryKey, BootstrapCrossSigningResult, CrossSigningKeyExport,
56 CrossSigningStatus, DecodeError, DecryptedEvent, Device, DeviceLists, EncryptionSettings,
57 EventEncryptionAlgorithm, KeyImportError, KeysImportResult, MegolmV1BackupKey,
58 ProgressListener, Request, RequestType, RequestVerificationResult, RoomKeyCounts, RoomSettings,
59 Sas, SignatureUploadRequest, StartSasResult, UserIdentity, Verification, VerificationRequest,
60 dehydrated_devices::DehydratedDevices,
61 error::{
62 BootstrapCrossSigningError, CryptoStoreError, DecryptionError, SecretImportError,
63 SecretsBundleExportError, SignatureError,
64 },
65 parse_user_id,
66 responses::{OwnedResponse, response_from_string},
67};
68
69/// The return value for the [`OlmMachine::receive_sync_changes()`] method.
70///
71/// Will contain various information about the `/sync` changes the
72/// [`OlmMachine`] processed.
73#[derive(uniffi::Record)]
74pub struct SyncChangesResult {
75 /// The, now possibly decrypted, to-device events the [`OlmMachine`]
76 /// received, decrypted, and processed.
77 to_device_events: Vec<String>,
78
79 /// Information about the room keys that were extracted out of the to-device
80 /// events.
81 room_key_infos: Vec<RoomKeyInfo>,
82}
83
84/// Information on a room key that has been received or imported.
85#[derive(uniffi::Record)]
86pub struct RoomKeyInfo {
87 /// The [messaging algorithm] that this key is used for. Will be one of the
88 /// `m.megolm.*` algorithms.
89 ///
90 /// [messaging algorithm]: https://spec.matrix.org/v1.6/client-server-api/#messaging-algorithms
91 pub algorithm: String,
92
93 /// The room where the key is used.
94 pub room_id: String,
95
96 /// The Curve25519 key of the device which initiated the session originally.
97 pub sender_key: String,
98
99 /// The ID of the session that the key is for.
100 pub session_id: String,
101}
102
103impl From<matrix_sdk_crypto::store::types::RoomKeyInfo> for RoomKeyInfo {
104 fn from(value: matrix_sdk_crypto::store::types::RoomKeyInfo) -> Self {
105 Self {
106 algorithm: value.algorithm.to_string(),
107 room_id: value.room_id.to_string(),
108 sender_key: value.sender_key.to_base64(),
109 session_id: value.session_id,
110 }
111 }
112}
113
114/// A high level state machine that handles E2EE for Matrix.
115#[derive(uniffi::Object)]
116pub struct OlmMachine {
117 pub(crate) inner: ManuallyDrop<InnerMachine>,
118 pub(crate) runtime: Runtime,
119}
120
121impl Drop for OlmMachine {
122 fn drop(&mut self) {
123 // Dropping the inner OlmMachine must happen within a tokio context
124 // because deadpool drops sqlite connections in the DB pool on tokio's
125 // blocking threadpool to avoid blocking async worker threads.
126 let _guard = self.runtime.enter();
127 // SAFETY: self.inner is never used again, which is the only requirement
128 // for ManuallyDrop::drop to be used safely.
129 unsafe {
130 ManuallyDrop::drop(&mut self.inner);
131 }
132 }
133}
134
135/// A pair of outgoing room key requests, both of those are sendToDevice
136/// requests.
137#[derive(uniffi::Record)]
138pub struct KeyRequestPair {
139 /// The optional cancellation, this is None if no previous key request was
140 /// sent out for this key, thus it doesn't need to be cancelled.
141 pub cancellation: Option<Request>,
142 /// The actual key request.
143 pub key_request: Request,
144}
145
146/// The result of a signature verification of a signed JSON object.
147#[derive(Clone, Debug, PartialEq, Eq, uniffi::Record)]
148pub struct SignatureVerification {
149 /// The result of the signature verification using the public key of our own
150 /// device.
151 pub device_signature: SignatureState,
152 /// The result of the signature verification using the public key of our own
153 /// user identity.
154 pub user_identity_signature: SignatureState,
155 /// The result of the signature verification using public keys of other
156 /// devices we own.
157 pub other_devices_signatures: HashMap<String, SignatureState>,
158 /// Is the signed JSON object trusted.
159 ///
160 /// This flag tells us if the result has a valid signature from any of the
161 /// following:
162 ///
163 /// - Our own device
164 /// - Our own user identity, provided the identity is trusted as well
165 /// - Any of our own devices, provided the device is trusted as well
166 pub trusted: bool,
167}
168
169impl From<RustSignatureCheckResult> for SignatureVerification {
170 fn from(r: RustSignatureCheckResult) -> Self {
171 let trusted = r.trusted();
172
173 Self {
174 device_signature: r.device_signature,
175 user_identity_signature: r.user_identity_signature,
176 other_devices_signatures: r
177 .other_signatures
178 .into_iter()
179 .map(|(k, v)| (k.to_string(), v))
180 .collect(),
181 trusted,
182 }
183 }
184}
185
186#[matrix_sdk_ffi_macros::export]
187impl OlmMachine {
188 /// Create a new `OlmMachine`
189 ///
190 /// # Arguments
191 ///
192 /// - `user_id` - The unique ID of the user that owns this machine.
193 /// - `device_id` - The unique ID of the device that owns this machine.
194 /// - `path` - The path where the state of the machine should be persisted.
195 /// - `passphrase` - The passphrase that should be used to encrypt the data
196 /// at rest in the crypto store. **Warning**, if no passphrase is given,
197 /// the store and all its data will remain unencrypted.
198 #[uniffi::constructor]
199 pub fn new(
200 user_id: String,
201 device_id: String,
202 path: String,
203 mut passphrase: Option<String>,
204 ) -> Result<Arc<Self>, CryptoStoreError> {
205 let user_id = parse_user_id(&user_id)?;
206 let device_id = device_id.as_str().into();
207 let runtime = Runtime::new().expect("Couldn't create a tokio runtime");
208
209 let store = runtime
210 .block_on(matrix_sdk_sqlite::SqliteCryptoStore::open(path, passphrase.as_deref()))?;
211
212 passphrase.zeroize();
213
214 let inner = runtime.block_on(
215 OlmMachineBuilder::new(&user_id, device_id).with_crypto_store(Arc::new(store)).build(),
216 )?;
217
218 Ok(Arc::new(OlmMachine { inner: ManuallyDrop::new(inner), runtime }))
219 }
220
221 /// Get the user ID of the owner of this `OlmMachine`.
222 pub fn user_id(&self) -> String {
223 self.inner.user_id().to_string()
224 }
225
226 /// Get the device ID of the device of this `OlmMachine`.
227 pub fn device_id(&self) -> String {
228 self.inner.device_id().to_string()
229 }
230
231 /// Get our own identity keys.
232 pub fn identity_keys(&self) -> HashMap<String, String> {
233 let identity_keys = self.inner.identity_keys();
234 let curve_key = identity_keys.curve25519.to_base64();
235 let ed25519_key = identity_keys.ed25519.to_base64();
236
237 HashMap::from([("ed25519".to_owned(), ed25519_key), ("curve25519".to_owned(), curve_key)])
238 }
239
240 /// Get the status of the private cross signing keys.
241 ///
242 /// This can be used to check which private cross signing keys we have
243 /// stored locally.
244 pub fn cross_signing_status(&self) -> CrossSigningStatus {
245 self.runtime.block_on(self.inner.cross_signing_status()).into()
246 }
247
248 /// Get a cross signing user identity for the given user ID.
249 ///
250 /// # Arguments
251 ///
252 /// - `user_id` - The unique id of the user that the identity belongs to
253 /// - `timeout` - The time in seconds we should wait before returning if the
254 /// user's device list has been marked as stale. Passing a 0 as the
255 /// timeout means that we won't wait at all. **Note**, this assumes that
256 /// the requests from [`OlmMachine::outgoing_requests`] are being
257 /// processed and sent out. Namely, this waits for a `/keys/query`
258 /// response to be received.
259 pub fn get_identity(
260 &self,
261 user_id: String,
262 timeout: u32,
263 ) -> Result<Option<UserIdentity>, CryptoStoreError> {
264 let user_id = parse_user_id(&user_id)?;
265
266 let timeout = if timeout == 0 { None } else { Some(Duration::from_secs(timeout.into())) };
267
268 Ok(
269 if let Some(identity) =
270 self.runtime.block_on(self.inner.get_identity(&user_id, timeout))?
271 {
272 Some(self.runtime.block_on(UserIdentity::from_rust(identity))?)
273 } else {
274 None
275 },
276 )
277 }
278
279 /// Check if a user identity is considered to be verified by us.
280 pub fn is_identity_verified(&self, user_id: String) -> Result<bool, CryptoStoreError> {
281 let user_id = parse_user_id(&user_id)?;
282
283 Ok(
284 if let Some(identity) =
285 self.runtime.block_on(self.inner.get_identity(&user_id, None))?
286 {
287 identity.is_verified()
288 } else {
289 false
290 },
291 )
292 }
293
294 /// Manually the user with the given user ID.
295 ///
296 /// This method will attempt to sign the user identity using either our
297 /// private cross signing key, for other user identities, or our device keys
298 /// for our own user identity.
299 ///
300 /// This method can fail if we don't have the private part of our
301 /// user-signing key.
302 ///
303 /// Returns a request that needs to be sent out for the user identity to be
304 /// marked as verified.
305 pub fn verify_identity(
306 &self,
307 user_id: String,
308 ) -> Result<SignatureUploadRequest, SignatureError> {
309 let user_id = UserId::parse(user_id)?;
310
311 let user_identity = self.runtime.block_on(self.inner.get_identity(&user_id, None))?;
312
313 if let Some(user_identity) = user_identity {
314 Ok(match user_identity {
315 SdkUserIdentity::Own(i) => self.runtime.block_on(i.verify())?,
316 SdkUserIdentity::Other(i) => self.runtime.block_on(i.verify())?,
317 }
318 .into())
319 } else {
320 Err(SignatureError::UnknownUserIdentity(user_id.to_string()))
321 }
322 }
323
324 /// Get a `Device` from the store.
325 ///
326 /// # Arguments
327 ///
328 /// - `user_id` - The id of the device owner.
329 /// - `device_id` - The id of the device itself.
330 /// - `timeout` - The time in seconds we should wait before returning if the
331 /// user's device list has been marked as stale. Passing a 0 as the
332 /// timeout means that we won't wait at all. **Note**, this assumes that
333 /// the requests from [`OlmMachine::outgoing_requests`] are being
334 /// processed and sent out. Namely, this waits for a `/keys/query`
335 /// response to be received.
336 pub fn get_device(
337 &self,
338 user_id: String,
339 device_id: String,
340 timeout: u32,
341 ) -> Result<Option<Device>, CryptoStoreError> {
342 let user_id = parse_user_id(&user_id)?;
343
344 let timeout = if timeout == 0 { None } else { Some(Duration::from_secs(timeout.into())) };
345
346 Ok(self
347 .runtime
348 .block_on(self.inner.get_device(&user_id, device_id.as_str().into(), timeout))?
349 .map(|d| d.into()))
350 }
351
352 /// Manually verify the device of the given user with the given device ID.
353 ///
354 /// This method will attempt to sign the device using our private cross
355 /// signing key.
356 ///
357 /// This method will always fail if the device belongs to someone else, we
358 /// can only sign our own devices.
359 ///
360 /// It can also fail if we don't have the private part of our self-signing
361 /// key.
362 ///
363 /// Returns a request that needs to be sent out for the device to be marked
364 /// as verified.
365 pub fn verify_device(
366 &self,
367 user_id: String,
368 device_id: String,
369 ) -> Result<SignatureUploadRequest, SignatureError> {
370 let user_id = UserId::parse(user_id)?;
371 let device = self.runtime.block_on(self.inner.get_device(
372 &user_id,
373 device_id.as_str().into(),
374 None,
375 ))?;
376
377 if let Some(device) = device {
378 Ok(self.runtime.block_on(device.verify())?.into())
379 } else {
380 Err(SignatureError::UnknownDevice(user_id, device_id))
381 }
382 }
383
384 /// Set local trust state for the device of the given user without creating
385 /// or uploading any signatures if verified
386 pub fn set_local_trust(
387 &self,
388 user_id: String,
389 device_id: String,
390 trust_state: LocalTrust,
391 ) -> Result<(), CryptoStoreError> {
392 let user_id = parse_user_id(&user_id)?;
393
394 let device = self.runtime.block_on(self.inner.get_device(
395 &user_id,
396 device_id.as_str().into(),
397 None,
398 ))?;
399
400 if let Some(device) = device {
401 self.runtime.block_on(device.set_local_trust(trust_state))?;
402 }
403
404 Ok(())
405 }
406
407 /// Get all devices of an user.
408 ///
409 /// # Arguments
410 ///
411 /// - `user_id` - The id of the device owner.
412 /// - `timeout` - The time in seconds we should wait before returning if the
413 /// user's device list has been marked as stale. Passing a 0 as the
414 /// timeout means that we won't wait at all. **Note**, this assumes that
415 /// the requests from [`OlmMachine::outgoing_requests`] are being
416 /// processed and sent out. Namely, this waits for a `/keys/query`
417 /// response to be received.
418 pub fn get_user_devices(
419 &self,
420 user_id: String,
421 timeout: u32,
422 ) -> Result<Vec<Device>, CryptoStoreError> {
423 let user_id = parse_user_id(&user_id)?;
424
425 let timeout = if timeout == 0 { None } else { Some(Duration::from_secs(timeout.into())) };
426 Ok(self
427 .runtime
428 .block_on(self.inner.get_user_devices(&user_id, timeout))?
429 .devices()
430 .map(|d| d.into())
431 .collect())
432 }
433
434 /// Get the list of outgoing requests that need to be sent to the
435 /// homeserver.
436 ///
437 /// After the request was sent out and a successful response was received
438 /// the response body should be passed back to the state machine using the
439 /// [mark_request_as_sent()](Self::mark_request_as_sent) method.
440 ///
441 /// **Note**: This method call should be locked per call.
442 pub fn outgoing_requests(&self) -> Result<Vec<Request>, CryptoStoreError> {
443 Ok(self
444 .runtime
445 .block_on(self.inner.outgoing_requests())?
446 .into_iter()
447 .map(|r| r.into())
448 .collect())
449 }
450
451 /// Mark a request that was sent to the server as sent.
452 ///
453 /// # Arguments
454 ///
455 /// - `request_id` - The unique ID of the request that was sent out. This
456 /// needs to be an UUID.
457 ///
458 /// - `request_type` - The type of the request that was sent out.
459 /// - `response_body` - The body of the response that was received.
460 pub fn mark_request_as_sent(
461 &self,
462 request_id: String,
463 request_type: RequestType,
464 response_body: String,
465 ) -> Result<(), CryptoStoreError> {
466 let id: OwnedTransactionId = request_id.into();
467
468 let response = response_from_string(&response_body);
469
470 let response: OwnedResponse = match request_type {
471 RequestType::KeysUpload => {
472 KeysUploadResponse::try_from_http_response(response).map(Into::into)
473 }
474 RequestType::KeysQuery => {
475 KeysQueryResponse::try_from_http_response(response).map(Into::into)
476 }
477 RequestType::ToDevice => {
478 ToDeviceResponse::try_from_http_response(response).map(Into::into)
479 }
480 RequestType::KeysClaim => {
481 KeysClaimResponse::try_from_http_response(response).map(Into::into)
482 }
483 RequestType::SignatureUpload => {
484 SignatureUploadResponse::try_from_http_response(response).map(Into::into)
485 }
486 RequestType::KeysBackup => {
487 KeysBackupResponse::try_from_http_response(response).map(Into::into)
488 }
489 RequestType::RoomMessage => {
490 RoomMessageResponse::try_from_http_response(response).map(Into::into)
491 }
492 }
493 .expect("Can't convert json string to response");
494
495 self.runtime.block_on(self.inner.mark_request_as_sent(&id, &response))?;
496
497 Ok(())
498 }
499
500 /// Let the state machine know about E2EE related sync changes that we
501 /// received from the server.
502 ///
503 /// This needs to be called after every sync, ideally before processing any
504 /// other sync changes.
505 ///
506 /// # Arguments
507 ///
508 /// - `events` - A serialized array of to-device events we received in the
509 /// current sync response.
510 ///
511 /// - `device_changes` - The list of devices that have changed in some way
512 /// since the previous sync.
513 ///
514 /// - `key_counts` - The map of uploaded one-time key types and counts.
515 pub fn receive_sync_changes(
516 &self,
517 events: String,
518 device_changes: DeviceLists,
519 key_counts: HashMap<String, i32>,
520 unused_fallback_keys: Option<Vec<String>>,
521 next_batch_token: String,
522 decryption_settings: &DecryptionSettings,
523 ) -> Result<SyncChangesResult, CryptoStoreError> {
524 let to_device: ToDevice = serde_json::from_str(&events)?;
525 let device_changes: RumaDeviceLists = device_changes.into();
526 let key_counts: BTreeMap<OneTimeKeyAlgorithm, UInt> = key_counts
527 .into_iter()
528 .map(|(k, v)| {
529 (
530 OneTimeKeyAlgorithm::from(k),
531 v.clamp(0, i32::MAX)
532 .try_into()
533 .expect("Couldn't convert key counts into an UInt"),
534 )
535 })
536 .collect();
537
538 let unused_fallback_keys: Option<Vec<OneTimeKeyAlgorithm>> =
539 unused_fallback_keys.map(|u| u.into_iter().map(OneTimeKeyAlgorithm::from).collect());
540
541 let (to_device_events, room_key_infos) =
542 self.runtime.block_on(self.inner.receive_sync_changes(
543 matrix_sdk_crypto::EncryptionSyncChanges {
544 to_device_events: to_device.events,
545 changed_devices: &device_changes,
546 one_time_keys_counts: &key_counts,
547 unused_fallback_keys: unused_fallback_keys.as_deref(),
548 next_batch_token: Some(next_batch_token),
549 },
550 decryption_settings,
551 ))?;
552
553 let to_device_events = to_device_events
554 .into_iter()
555 .map(|event| event.to_raw().json().get().to_owned())
556 .collect();
557 let room_key_infos = room_key_infos.into_iter().map(|info| info.into()).collect();
558
559 Ok(SyncChangesResult { to_device_events, room_key_infos })
560 }
561
562 /// Add the given list of users to be tracked, triggering a key query
563 /// request for them.
564 ///
565 /// The OlmMachine maintains a list of users whose devices we are keeping
566 /// track of: these are known as "tracked users". These must be users that
567 /// we share a room with, so that the server sends us updates for their
568 /// device lists.
569 ///
570 /// _Note_: Only users that aren't already tracked will be considered for an
571 /// update. It's safe to call this with already tracked users, it won't
572 /// result in excessive `/keys/query` requests.
573 ///
574 /// # Arguments
575 ///
576 /// `users` - The users that should be queued up for a key query.
577 pub fn update_tracked_users(&self, users: Vec<String>) -> Result<(), CryptoStoreError> {
578 let users: Vec<OwnedUserId> =
579 users.into_iter().filter_map(|u| UserId::parse(u).ok()).collect();
580
581 self.runtime.block_on(self.inner.update_tracked_users(users.iter().map(Deref::deref)))?;
582
583 Ok(())
584 }
585
586 /// Check if the given user is considered to be tracked.
587 ///
588 /// A user can be marked for tracking using the
589 /// [`OlmMachine::update_tracked_users()`] method.
590 pub fn is_user_tracked(&self, user_id: String) -> Result<bool, CryptoStoreError> {
591 let user_id = parse_user_id(&user_id)?;
592 Ok(self.runtime.block_on(self.inner.tracked_users())?.contains(&user_id))
593 }
594
595 /// Generate one-time key claiming requests for all the users we are missing
596 /// sessions for.
597 ///
598 /// After the request was sent out and a successful response was received
599 /// the response body should be passed back to the state machine using the
600 /// [mark_request_as_sent()](Self::mark_request_as_sent) method.
601 ///
602 /// This method should be called every time before a call to
603 /// [`share_room_key()`](Self::share_room_key) is made.
604 ///
605 /// # Arguments
606 ///
607 /// - `users` - The list of users for which we would like to establish 1:1
608 /// Olm sessions for.
609 pub fn get_missing_sessions(
610 &self,
611 users: Vec<String>,
612 ) -> Result<Option<Request>, CryptoStoreError> {
613 let users: Vec<OwnedUserId> =
614 users.into_iter().filter_map(|u| UserId::parse(u).ok()).collect();
615
616 Ok(self
617 .runtime
618 .block_on(self.inner.get_missing_sessions(users.iter().map(Deref::deref)))?
619 .map(|r| r.into()))
620 }
621
622 /// Get the stored room settings, such as the encryption algorithm or
623 /// whether to encrypt only for trusted devices.
624 ///
625 /// These settings can be modified via
626 /// [set_room_algorithm()](Self::set_room_algorithm) and
627 /// [set_room_only_allow_trusted_devices()](Self::set_room_only_allow_trusted_devices)
628 /// methods.
629 pub fn get_room_settings(
630 &self,
631 room_id: String,
632 ) -> Result<Option<RoomSettings>, CryptoStoreError> {
633 let room_id = RoomId::parse(room_id)?;
634 let settings = self
635 .runtime
636 .block_on(self.inner.store().get_room_settings(&room_id))?
637 .map(|v| v.try_into())
638 .transpose()?;
639 Ok(settings)
640 }
641
642 /// Set the room algorithm used for encrypting messages to one of the
643 /// available variants
644 pub fn set_room_algorithm(
645 &self,
646 room_id: String,
647 algorithm: EventEncryptionAlgorithm,
648 ) -> Result<(), CryptoStoreError> {
649 let room_id = RoomId::parse(room_id)?;
650 self.runtime.block_on(async move {
651 let mut settings =
652 self.inner.store().get_room_settings(&room_id).await?.unwrap_or_default();
653 settings.algorithm = algorithm.into();
654 self.inner
655 .store()
656 .save_changes(Changes {
657 room_settings: HashMap::from([(room_id, settings)]),
658 ..Default::default()
659 })
660 .await?;
661 Ok(())
662 })
663 }
664
665 /// Set flag whether this room should encrypt messages for untrusted
666 /// devices, or whether they should be excluded from the conversation.
667 ///
668 /// Note that per-room setting may be overridden by a global
669 /// [set_only_allow_trusted_devices()](Self::set_only_allow_trusted_devices)
670 /// method.
671 pub fn set_room_only_allow_trusted_devices(
672 &self,
673 room_id: String,
674 only_allow_trusted_devices: bool,
675 ) -> Result<(), CryptoStoreError> {
676 let room_id = RoomId::parse(room_id)?;
677 self.runtime.block_on(async move {
678 let mut settings =
679 self.inner.store().get_room_settings(&room_id).await?.unwrap_or_default();
680 settings.only_allow_trusted_devices = only_allow_trusted_devices;
681 self.inner
682 .store()
683 .save_changes(Changes {
684 room_settings: HashMap::from([(room_id, settings)]),
685 ..Default::default()
686 })
687 .await?;
688 Ok(())
689 })
690 }
691
692 /// Check whether there is a global flag to only encrypt messages for
693 /// trusted devices or for everyone.
694 ///
695 /// Note that if the global flag is false, individual rooms may still be
696 /// encrypting only for trusted devices, depending on the per-room
697 /// `only_allow_trusted_devices` flag.
698 pub fn get_only_allow_trusted_devices(&self) -> Result<bool, CryptoStoreError> {
699 let block = self.runtime.block_on(self.inner.store().get_only_allow_trusted_devices())?;
700 Ok(block)
701 }
702
703 /// Set global flag whether to encrypt messages for untrusted devices, or
704 /// whether they should be excluded from the conversation.
705 ///
706 /// Note that if enabled, it will override any per-room settings.
707 pub fn set_only_allow_trusted_devices(
708 &self,
709 only_allow_trusted_devices: bool,
710 ) -> Result<(), CryptoStoreError> {
711 self.runtime.block_on(
712 self.inner.store().set_only_allow_trusted_devices(only_allow_trusted_devices),
713 )?;
714 Ok(())
715 }
716
717 /// Share a room key with the given list of users for the given room.
718 ///
719 /// After the request was sent out and a successful response was received
720 /// the response body should be passed back to the state machine using the
721 /// [mark_request_as_sent()](Self::mark_request_as_sent) method.
722 ///
723 /// This method should be called every time before a call to
724 /// [`encrypt()`](Self::encrypt) with the given `room_id` is made.
725 ///
726 /// # Arguments
727 ///
728 /// - `room_id` - The unique id of the room, note that this doesn't strictly
729 /// need to be a Matrix room, it just needs to be an unique identifier for
730 /// the group that will participate in the conversation.
731 ///
732 /// - `users` - The list of users which are considered to be members of the
733 /// room and should receive the room key.
734 ///
735 /// - `settings` - The settings that should be used for the room key.
736 pub fn share_room_key(
737 &self,
738 room_id: String,
739 users: Vec<String>,
740 settings: EncryptionSettings,
741 ) -> Result<Vec<Request>, CryptoStoreError> {
742 let users: Vec<OwnedUserId> =
743 users.into_iter().filter_map(|u| UserId::parse(u).ok()).collect();
744
745 let room_id = RoomId::parse(room_id)?;
746 let requests = self.runtime.block_on(self.inner.share_room_key(
747 &room_id,
748 users.iter().map(Deref::deref),
749 settings,
750 ))?;
751
752 Ok(requests.into_iter().map(|r| r.as_ref().into()).collect())
753 }
754
755 /// Encrypt the given event with the given type and content for the given
756 /// room.
757 ///
758 /// **Note**: A room key needs to be shared with the group of users that are
759 /// members in the given room. If this is not done this method will panic.
760 ///
761 /// The usual flow to encrypt an event using this state machine is as
762 /// follows:
763 ///
764 /// 1. Get the one-time key claim request to establish 1:1 Olm sessions for
765 /// the room members of the room we wish to participate in. This is done
766 /// using the [`get_missing_sessions()`](Self::get_missing_sessions)
767 /// method. This method call should be locked per call.
768 ///
769 /// 2. Share a room key with all the room members using the
770 /// [`share_room_key()`](Self::share_room_key). This method call should
771 /// be locked per room.
772 ///
773 /// 3. Encrypt the event using this method.
774 /// 4. Send the encrypted event to the server.
775 ///
776 /// After the room key is shared steps 1 and 2 will become noops, unless
777 /// there's some changes in the room membership or in the list of devices a
778 /// member has.
779 ///
780 /// # Arguments
781 ///
782 /// - `room_id` - The unique id of the room where the event will be sent to.
783 /// - `even_type` - The type of the event.
784 /// - `content` - The serialized content of the event.
785 pub fn encrypt(
786 &self,
787 room_id: String,
788 event_type: String,
789 content: String,
790 ) -> Result<String, CryptoStoreError> {
791 let room_id = RoomId::parse(room_id)?;
792 let content = serde_json::from_str(&content)?;
793
794 let result = self
795 .runtime
796 .block_on(self.inner.encrypt_room_event_raw(&room_id, &event_type, &content))
797 .expect("Encrypting an event produced an error");
798
799 Ok(serde_json::to_string(&result.content)?)
800 }
801
802 /// Encrypt the given event with the given type and content for the given
803 /// device. This method is used to send an event to a specific device.
804 ///
805 /// # Arguments
806 ///
807 /// - `user_id` - The ID of the user who owns the target device.
808 /// - `device_id` - The ID of the device to which the message will be sent.
809 /// - `event_type` - The event type.
810 /// - `content` - The serialized content of the event.
811 ///
812 /// # Returns
813 ///
814 /// A `Result` containing the request to be sent out if the encryption was
815 /// successful. If the device is not found, the result will be `Ok(None)`.
816 ///
817 /// The caller should ensure that there is an olm session (see
818 /// `get_missing_sessions`) with the target device before calling this
819 /// method.
820 pub fn create_encrypted_to_device_request(
821 &self,
822 user_id: String,
823 device_id: String,
824 event_type: String,
825 content: String,
826 share_strategy: CollectStrategy,
827 ) -> Result<Option<Request>, CryptoStoreError> {
828 let user_id = parse_user_id(&user_id)?;
829 let device_id = device_id.as_str().into();
830 let content = serde_json::from_str(&content)?;
831
832 let device = self.runtime.block_on(self.inner.get_device(&user_id, device_id, None))?;
833
834 if let Some(device) = device {
835 let encrypted_content = self.runtime.block_on(device.encrypt_event_raw(
836 &event_type,
837 &content,
838 share_strategy,
839 ))?;
840
841 let request = ToDeviceRequest::new(
842 user_id.as_ref(),
843 DeviceIdOrAllDevices::DeviceId(device_id.to_owned()),
844 "m.room.encrypted",
845 encrypted_content.cast(),
846 );
847
848 Ok(Some(request.into()))
849 } else {
850 Ok(None)
851 }
852 }
853
854 /// Decrypt the given event that was sent in the given room.
855 ///
856 /// # Arguments
857 ///
858 /// - `event` - The serialized encrypted version of the event.
859 /// - `room_id` - The unique id of the room where the event was sent to.
860 /// - `handle_verification_events` - if the supplied event is a verification
861 /// event, use it to update the verification state. **Note**: it is
862 /// recommended to avoid setting this flag to true and use the explicit
863 /// [`OlmMachine::receive_verification_event`] method instead:
864 /// verification events sometimes need preparation before we can handle
865 /// them: see the documentation for
866 /// [`OlmMachine::receive_verification_event`].
867 ///
868 /// - `strict_shields` - If `true`, messages will be decorated with strict
869 /// warnings (use `false` to match legacy behaviour where unsafe keys have
870 /// lower severity warnings and unverified identities are not decorated).
871 ///
872 /// - `decryption_settings` - The setting for decrypting messages.
873 pub fn decrypt_room_event(
874 &self,
875 event: String,
876 room_id: String,
877 handle_verification_events: bool,
878 strict_shields: bool,
879 decryption_settings: DecryptionSettings,
880 ) -> Result<DecryptedEvent, DecryptionError> {
881 // Element Android wants only the content and the type and will create a
882 // decrypted event with those two itself, this struct makes sure we
883 // throw away all the other fields.
884 #[derive(Deserialize, Serialize)]
885 struct Event<'a> {
886 #[serde(rename = "type")]
887 event_type: String,
888 #[serde(borrow)]
889 content: &'a RawValue,
890 }
891
892 let event: Raw<_> = serde_json::from_str(&event)?;
893 let room_id = RoomId::parse(room_id)?;
894
895 let decrypted = self.runtime.block_on(self.inner.decrypt_room_event(
896 &event,
897 &room_id,
898 &decryption_settings,
899 ))?;
900
901 if handle_verification_events
902 && let Ok(AnyTimelineEvent::MessageLike(e)) = decrypted.event.deserialize()
903 {
904 match &e {
905 AnyMessageLikeEvent::RoomMessage(MessageLikeEvent::Original(original_event)) => {
906 if let MessageType::VerificationRequest(_) = &original_event.content.msgtype {
907 self.runtime.block_on(self.inner.receive_verification_event(&e))?;
908 }
909 }
910 _ if e.event_type().to_string().starts_with("m.key.verification") => {
911 self.runtime.block_on(self.inner.receive_verification_event(&e))?;
912 }
913 _ => (),
914 }
915 }
916
917 let encryption_info = decrypted.encryption_info;
918
919 let event_json: Event<'_> = serde_json::from_str(decrypted.event.json().get())?;
920
921 Ok(match &encryption_info.algorithm_info {
922 AlgorithmInfo::MegolmV1AesSha2 {
923 curve25519_key,
924 sender_claimed_keys,
925 session_id: _,
926 } => DecryptedEvent {
927 clear_event: serde_json::to_string(&event_json)?,
928 sender_curve25519_key: curve25519_key.to_owned(),
929 claimed_ed25519_key: sender_claimed_keys.get(&DeviceKeyAlgorithm::Ed25519).cloned(),
930 forwarding_curve25519_chain: vec![],
931 shield_state: if strict_shields {
932 encryption_info.verification_state.to_shield_state_strict().into()
933 } else {
934 encryption_info.verification_state.to_shield_state_lax().into()
935 },
936 },
937 AlgorithmInfo::OlmV1Curve25519AesSha2 { .. } => {
938 // cannot happen because `decrypt_room_event` would have fail to
939 // decrypt olm for a room (EventError::UnsupportedAlgorithm)
940 panic!("Unsupported olm algorithm in room")
941 }
942 })
943 }
944
945 /// Request or re-request a room key that was used to encrypt the given
946 /// event.
947 ///
948 /// # Arguments
949 ///
950 /// - `event` - The undecryptable event that we would wish to request a room
951 /// key for.
952 ///
953 /// - `room_id` - The id of the room the event was sent to.
954 pub fn request_room_key(
955 &self,
956 event: String,
957 room_id: String,
958 ) -> Result<KeyRequestPair, DecryptionError> {
959 let event: Raw<_> = serde_json::from_str(&event)?;
960 let room_id = RoomId::parse(room_id)?;
961
962 let (cancel, request) =
963 self.runtime.block_on(self.inner.request_room_key(&event, &room_id))?;
964
965 let cancellation = cancel.map(|r| r.into());
966 let key_request = request.into();
967
968 Ok(KeyRequestPair { cancellation, key_request })
969 }
970
971 /// Export all of our room keys.
972 ///
973 /// # Arguments
974 ///
975 /// - `passphrase` - The passphrase that should be used to encrypt the key
976 /// export.
977 ///
978 /// - `rounds` - The number of rounds that should be used when expanding the
979 /// passphrase into an key.
980 pub fn export_room_keys(
981 &self,
982 passphrase: String,
983 rounds: i32,
984 ) -> Result<String, CryptoStoreError> {
985 let keys = self.runtime.block_on(self.inner.store().export_room_keys(|_| true))?;
986
987 let encrypted = encrypt_room_key_export(&keys, &passphrase, rounds as u32)
988 .map_err(CryptoStoreError::Serialization)?;
989
990 Ok(encrypted)
991 }
992
993 /// Import room keys from the given serialized key export.
994 ///
995 /// # Arguments
996 ///
997 /// - `keys` - The serialized version of the key export.
998 /// - `passphrase` - The passphrase that was used to encrypt the key export.
999 /// - `progress_listener` - A callback that can be used to introspect the
1000 /// progress of the key import.
1001 pub fn import_room_keys(
1002 &self,
1003 keys: String,
1004 passphrase: String,
1005 progress_listener: Box<dyn ProgressListener>,
1006 ) -> Result<KeysImportResult, KeyImportError> {
1007 let keys = Cursor::new(keys);
1008 let keys = decrypt_room_key_export(keys, &passphrase)?;
1009 self.import_room_keys_helper(keys, None, progress_listener)
1010 }
1011
1012 /// Import room keys from the given serialized unencrypted key export.
1013 ///
1014 /// This method is the same as [`OlmMachine::import_room_keys`] but the
1015 /// decryption step is skipped and should be performed by the caller. This
1016 /// should be used if the room keys are coming from the server-side backup,
1017 /// the method will mark all imported room keys as backed up.
1018 ///
1019 /// **Note**: This has been deprecated. Use
1020 /// [`OlmMachine::import_room_keys_from_backup`] instead.
1021 ///
1022 /// # Arguments
1023 ///
1024 /// - `keys` - The serialized version of the unencrypted key export.
1025 /// - `progress_listener` - A callback that can be used to introspect the
1026 /// progress of the key import.
1027 pub fn import_decrypted_room_keys(
1028 &self,
1029 keys: String,
1030 progress_listener: Box<dyn ProgressListener>,
1031 ) -> Result<KeysImportResult, KeyImportError> {
1032 // Assume that the keys came from the current backup version.
1033 let backup_version = self.runtime.block_on(self.inner.backup_machine().backup_version());
1034 let keys: Vec<Value> = serde_json::from_str(&keys)?;
1035 let keys = keys.into_iter().map(serde_json::from_value).filter_map(|k| k.ok()).collect();
1036 self.import_room_keys_helper(keys, backup_version.as_deref(), progress_listener)
1037 }
1038
1039 /// Import room keys from the given serialized unencrypted key export.
1040 ///
1041 /// This method is the same as [`OlmMachine::import_room_keys`] but the
1042 /// decryption step is skipped and should be performed by the caller. This
1043 /// should be used if the room keys are coming from the server-side backup.
1044 /// The method will mark all imported room keys as backed up.
1045 ///
1046 /// # Arguments
1047 ///
1048 /// - `keys` - The serialized version of the unencrypted key export.
1049 /// - `backup_version` - The version of the backup that these keys came
1050 /// from.
1051 ///
1052 /// - `progress_listener` - A callback that can be used to introspect the
1053 /// progress of the key import.
1054 pub fn import_room_keys_from_backup(
1055 &self,
1056 keys: String,
1057 backup_version: String,
1058 progress_listener: Box<dyn ProgressListener>,
1059 ) -> Result<KeysImportResult, KeyImportError> {
1060 let keys: Vec<Value> = serde_json::from_str(&keys)?;
1061 let keys = keys.into_iter().map(serde_json::from_value).filter_map(|k| k.ok()).collect();
1062 self.import_room_keys_helper(keys, Some(&backup_version), progress_listener)
1063 }
1064
1065 /// Discard the currently active room key for the given room if there is
1066 /// one.
1067 pub fn discard_room_key(&self, room_id: String) -> Result<(), CryptoStoreError> {
1068 let room_id = RoomId::parse(room_id)?;
1069
1070 self.runtime.block_on(self.inner.discard_room_key(&room_id))?;
1071
1072 Ok(())
1073 }
1074
1075 /// Receive an unencrypted verification event.
1076 ///
1077 /// This method can be used to pass verification events that are happening
1078 /// in unencrypted rooms to the `OlmMachine`.
1079 ///
1080 /// **Note**: This has been deprecated.
1081 pub fn receive_unencrypted_verification_event(
1082 &self,
1083 event: String,
1084 room_id: String,
1085 ) -> Result<(), CryptoStoreError> {
1086 self.receive_verification_event(event, room_id)
1087 }
1088
1089 /// Receive a verification event.
1090 ///
1091 /// This method can be used to pass verification events that are happening
1092 /// in rooms to the `OlmMachine`. The event should be in the decrypted form.
1093 ///
1094 /// **Note**: If the supplied event is an `m.room.message` event with
1095 /// `msgtype: m.key.verification.request`, then the device information for
1096 /// the sending user must be up-to-date before calling this method
1097 /// (otherwise, the request will be ignored). It is hard to guarantee this
1098 /// is the case, but you can maximize your chances by explicitly making a
1099 /// request to /keys/query for the user's device info, and processing the
1100 /// response with [`OlmMachine::mark_request_as_sent`].
1101 pub fn receive_verification_event(
1102 &self,
1103 event: String,
1104 room_id: String,
1105 ) -> Result<(), CryptoStoreError> {
1106 let room_id = RoomId::parse(room_id)?;
1107 let event: AnySyncMessageLikeEvent = serde_json::from_str(&event)?;
1108
1109 let event = event.into_full_event(room_id);
1110
1111 self.runtime.block_on(self.inner.receive_verification_event(&event))?;
1112
1113 Ok(())
1114 }
1115
1116 /// Get all the verification requests that we share with the given user.
1117 ///
1118 /// # Arguments
1119 ///
1120 /// - `user_id` - The ID of the user for which we would like to fetch the
1121 /// verification requests.
1122 pub fn get_verification_requests(&self, user_id: String) -> Vec<Arc<VerificationRequest>> {
1123 let Ok(user_id) = UserId::parse(user_id) else {
1124 return vec![];
1125 };
1126
1127 self.inner
1128 .get_verification_requests(&user_id)
1129 .into_iter()
1130 .map(|v| {
1131 VerificationRequest { inner: v, runtime: self.runtime.handle().to_owned() }.into()
1132 })
1133 .collect()
1134 }
1135
1136 /// Get a verification requests that we share with the given user with the
1137 /// given flow id.
1138 ///
1139 /// # Arguments
1140 ///
1141 /// - `user_id` - The ID of the user for which we would like to fetch the
1142 /// verification requests.
1143 ///
1144 /// - `flow_id` - The ID that uniquely identifies the verification flow.
1145 pub fn get_verification_request(
1146 &self,
1147 user_id: String,
1148 flow_id: String,
1149 ) -> Option<Arc<VerificationRequest>> {
1150 let user_id = UserId::parse(user_id).ok()?;
1151
1152 self.inner.get_verification_request(&user_id, flow_id).map(|v| {
1153 VerificationRequest { inner: v, runtime: self.runtime.handle().to_owned() }.into()
1154 })
1155 }
1156
1157 /// Get an m.key.verification.request content for the given user.
1158 ///
1159 /// # Arguments
1160 ///
1161 /// - `user_id` - The ID of the user which we would like to request to
1162 /// verify.
1163 ///
1164 /// - `methods` - The list of verification methods we want to advertise to
1165 /// support.
1166 pub fn verification_request_content(
1167 &self,
1168 user_id: String,
1169 methods: Vec<String>,
1170 ) -> Result<Option<String>, CryptoStoreError> {
1171 let user_id = parse_user_id(&user_id)?;
1172
1173 let identity = self.runtime.block_on(self.inner.get_identity(&user_id, None))?;
1174
1175 let methods = methods.into_iter().map(VerificationMethod::from).collect();
1176
1177 Ok(if let Some(identity) = identity.and_then(|i| i.other()) {
1178 let content = identity.verification_request_content(Some(methods));
1179 Some(serde_json::to_string(&content)?)
1180 } else {
1181 None
1182 })
1183 }
1184
1185 /// Request a verification flow to begin with the given user in the given
1186 /// room.
1187 ///
1188 /// # Arguments
1189 ///
1190 /// - `user_id` - The ID of the user which we would like to request to
1191 /// verify.
1192 ///
1193 /// - `room_id` - The ID of the room that represents a DM with the given
1194 /// user.
1195 ///
1196 /// - `event_id` - The event ID of the `m.key.verification.request` event
1197 /// that we sent out to request the verification to begin. The content for
1198 /// this request can be created using the [verification_request_content()]
1199 /// method.
1200 ///
1201 /// - `methods` - The list of verification methods we advertised as
1202 /// supported in the `m.key.verification.request` event.
1203 ///
1204 /// [verification_request_content()]: Self::verification_request_content
1205 pub fn request_verification(
1206 &self,
1207 user_id: String,
1208 room_id: String,
1209 event_id: String,
1210 methods: Vec<String>,
1211 ) -> Result<Option<Arc<VerificationRequest>>, CryptoStoreError> {
1212 let user_id = parse_user_id(&user_id)?;
1213 let event_id = EventId::parse(event_id)?;
1214 let room_id = RoomId::parse(room_id)?;
1215
1216 let identity = self.runtime.block_on(self.inner.get_identity(&user_id, None))?;
1217
1218 let methods = methods.into_iter().map(VerificationMethod::from).collect();
1219
1220 Ok(if let Some(identity) = identity.and_then(|i| i.other()) {
1221 let request = identity.request_verification(&room_id, &event_id, Some(methods));
1222
1223 Some(
1224 VerificationRequest { inner: request, runtime: self.runtime.handle().to_owned() }
1225 .into(),
1226 )
1227 } else {
1228 None
1229 })
1230 }
1231
1232 /// Request a verification flow to begin with the given user's device.
1233 ///
1234 /// # Arguments
1235 ///
1236 /// - `user_id` - The ID of the user which we would like to request to
1237 /// verify.
1238 ///
1239 /// - `device_id` - The ID of the device that we wish to verify.
1240 /// - `methods` - The list of verification methods we advertised as
1241 /// supported in the `m.key.verification.request` event.
1242 pub fn request_verification_with_device(
1243 &self,
1244 user_id: String,
1245 device_id: String,
1246 methods: Vec<String>,
1247 ) -> Result<Option<RequestVerificationResult>, CryptoStoreError> {
1248 let user_id = parse_user_id(&user_id)?;
1249 let device_id = device_id.as_str().into();
1250
1251 let methods = methods.into_iter().map(VerificationMethod::from).collect();
1252
1253 Ok(
1254 if let Some(device) =
1255 self.runtime.block_on(self.inner.get_device(&user_id, device_id, None))?
1256 {
1257 let (verification, request) = device.request_verification_with_methods(methods);
1258
1259 Some(RequestVerificationResult {
1260 verification: VerificationRequest {
1261 inner: verification,
1262 runtime: self.runtime.handle().to_owned(),
1263 }
1264 .into(),
1265 request: request.into(),
1266 })
1267 } else {
1268 None
1269 },
1270 )
1271 }
1272
1273 /// Request a verification flow to begin with our other devices.
1274 ///
1275 /// # Arguments
1276 ///
1277 /// `methods` - The list of verification methods we want to advertise to
1278 /// support.
1279 pub fn request_self_verification(
1280 &self,
1281 methods: Vec<String>,
1282 ) -> Result<Option<RequestVerificationResult>, CryptoStoreError> {
1283 let identity =
1284 self.runtime.block_on(self.inner.get_identity(self.inner.user_id(), None))?;
1285
1286 let methods = methods.into_iter().map(VerificationMethod::from).collect();
1287
1288 Ok(if let Some(identity) = identity.and_then(|i| i.own()) {
1289 let (verification, request) =
1290 self.runtime.block_on(identity.request_verification_with_methods(methods))?;
1291 Some(RequestVerificationResult {
1292 verification: VerificationRequest {
1293 inner: verification,
1294 runtime: self.runtime.handle().to_owned(),
1295 }
1296 .into(),
1297 request: request.into(),
1298 })
1299 } else {
1300 None
1301 })
1302 }
1303
1304 /// Get a verification flow object for the given user with the given flow
1305 /// id.
1306 ///
1307 /// # Arguments
1308 ///
1309 /// - `user_id` - The ID of the user for which we would like to fetch the
1310 /// verification.
1311 ///
1312 /// - `flow_id` - The ID that uniquely identifies the verification flow.
1313 pub fn get_verification(&self, user_id: String, flow_id: String) -> Option<Arc<Verification>> {
1314 let user_id = UserId::parse(user_id).ok()?;
1315
1316 self.inner
1317 .get_verification(&user_id, &flow_id)
1318 .map(|v| Verification { inner: v, runtime: self.runtime.handle().to_owned() }.into())
1319 }
1320
1321 /// Start short auth string verification with a device without going through
1322 /// a verification request first.
1323 ///
1324 /// **Note**: This has been largely deprecated and the
1325 /// [request_verification_with_device()] method should be used instead.
1326 ///
1327 /// # Arguments
1328 ///
1329 /// - `user_id` - The ID of the user for which we would like to start the
1330 /// SAS verification.
1331 ///
1332 /// - `device_id` - The ID of device we would like to verify.
1333 ///
1334 /// [request_verification_with_device()]: Self::request_verification_with_device
1335 pub fn start_sas_with_device(
1336 &self,
1337 user_id: String,
1338 device_id: String,
1339 ) -> Result<Option<StartSasResult>, CryptoStoreError> {
1340 let user_id = parse_user_id(&user_id)?;
1341 let device_id = device_id.as_str().into();
1342
1343 Ok(
1344 if let Some(device) =
1345 self.runtime.block_on(self.inner.get_device(&user_id, device_id, None))?
1346 {
1347 let (sas, request) = self.runtime.block_on(device.start_verification())?;
1348
1349 Some(StartSasResult {
1350 sas: Sas { inner: Box::new(sas), runtime: self.runtime.handle().to_owned() }
1351 .into(),
1352 request: request.into(),
1353 })
1354 } else {
1355 None
1356 },
1357 )
1358 }
1359
1360 /// Create a new private cross signing identity and create a request to
1361 /// upload the public part of it to the server.
1362 pub fn bootstrap_cross_signing(
1363 &self,
1364 ) -> Result<BootstrapCrossSigningResult, BootstrapCrossSigningError> {
1365 Ok(self.runtime.block_on(self.inner.bootstrap_cross_signing(true))?.into())
1366 }
1367
1368 /// Export all our private cross signing keys.
1369 ///
1370 /// The export will contain the seed for the ed25519 keys as a base64
1371 /// encoded string.
1372 ///
1373 /// This method returns `None` if we don't have any private cross signing
1374 /// keys.
1375 pub fn export_cross_signing_keys(
1376 &self,
1377 ) -> Result<Option<CrossSigningKeyExport>, CryptoStoreError> {
1378 Ok(self.runtime.block_on(self.inner.export_cross_signing_keys())?.map(|e| e.into()))
1379 }
1380
1381 /// Import our private cross signing keys.
1382 ///
1383 /// The export needs to contain the seed for the ed25519 keys as a base64
1384 /// encoded string.
1385 pub fn import_cross_signing_keys(
1386 &self,
1387 export: CrossSigningKeyExport,
1388 ) -> Result<(), SecretImportError> {
1389 self.runtime.block_on(self.inner.import_cross_signing_keys(export.into()))?;
1390
1391 Ok(())
1392 }
1393
1394 /// Export all the secrets we have in the store into a serialized
1395 /// SecretsBundle.
1396 ///
1397 /// This method will export all the private cross-signing keys and, if
1398 /// available, the private part of a backup key and its accompanying
1399 /// version.
1400 ///
1401 /// The method will fail if we don't have all three private cross-signing
1402 /// keys available.
1403 ///
1404 /// **Warning**: Only export this and share it with a trusted recipient,
1405 /// i.e. if an existing device is sharing this with a new device.
1406 pub fn export_secrets_bundle(&self) -> Result<String, SecretsBundleExportError> {
1407 let bundle = self.runtime.block_on(self.inner.store().export_secrets_bundle())?;
1408
1409 Ok(serde_json::to_string(&bundle)?)
1410 }
1411
1412 /// Request missing local secrets from our devices (cross signing private
1413 /// keys, megolm backup). This will ask the sdk to create outgoing request
1414 /// to get the missing secrets.
1415 ///
1416 /// The requests will be processed as soon as `outgoing_requests()` is
1417 /// called to process them.
1418 pub fn query_missing_secrets_from_other_sessions(&self) -> Result<bool, CryptoStoreError> {
1419 Ok(self.runtime.block_on(self.inner.query_missing_secrets_from_other_sessions())?)
1420 }
1421
1422 /// Activate the given backup key to be used with the given backup version.
1423 ///
1424 /// **Warning**: The caller needs to make sure that the given `BackupKey` is
1425 /// trusted, otherwise we might be encrypting room keys that a malicious
1426 /// party could decrypt.
1427 ///
1428 /// The [`OlmMachine::verify_backup`] method can be used to so.
1429 pub fn enable_backup_v1(
1430 &self,
1431 key: MegolmV1BackupKey,
1432 version: String,
1433 ) -> Result<(), DecodeError> {
1434 let backup_key = RustBackupKey::from_base64(&key.public_key)?;
1435 backup_key.set_version(version);
1436
1437 self.runtime.block_on(self.inner.backup_machine().enable_backup_v1(backup_key))?;
1438
1439 Ok(())
1440 }
1441
1442 /// Are we able to encrypt room keys.
1443 ///
1444 /// This returns true if we have an active `BackupKey` and backup version
1445 /// registered with the state machine.
1446 pub fn backup_enabled(&self) -> bool {
1447 self.runtime.block_on(self.inner.backup_machine().enabled())
1448 }
1449
1450 /// Disable and reset our backup state.
1451 ///
1452 /// This will remove any pending backup request, remove the backup key and
1453 /// reset the backup state of each room key we have.
1454 pub fn disable_backup(&self) -> Result<(), CryptoStoreError> {
1455 Ok(self.runtime.block_on(self.inner.backup_machine().disable_backup())?)
1456 }
1457
1458 /// Encrypt a batch of room keys and return a request that needs to be sent
1459 /// out to backup the room keys.
1460 pub fn backup_room_keys(&self) -> Result<Option<Request>, CryptoStoreError> {
1461 let request = self.runtime.block_on(self.inner.backup_machine().backup())?;
1462
1463 let request = request.map(|r| r.into());
1464
1465 Ok(request)
1466 }
1467
1468 /// Get the number of backed up room keys and the total number of room keys.
1469 pub fn room_key_counts(&self) -> Result<RoomKeyCounts, CryptoStoreError> {
1470 Ok(self.runtime.block_on(self.inner.backup_machine().room_key_counts())?.into())
1471 }
1472
1473 /// Store the recovery key in the crypto store.
1474 ///
1475 /// This is useful if the client wants to support gossiping of the backup
1476 /// key.
1477 pub fn save_recovery_key(
1478 &self,
1479 key: Option<Arc<BackupRecoveryKey>>,
1480 version: Option<String>,
1481 ) -> Result<(), CryptoStoreError> {
1482 let key = key.map(|k| {
1483 // We need to clone here due to FFI limitations but RecoveryKey does
1484 // not want to expose clone since it's private key material.
1485 let mut encoded = k.to_base64();
1486 let key = BackupDecryptionKey::from_base64(&encoded)
1487 .expect("Encoding and decoding from base64 should always work");
1488 encoded.zeroize();
1489 key
1490 });
1491 Ok(self.runtime.block_on(self.inner.backup_machine().save_decryption_key(key, version))?)
1492 }
1493
1494 /// Get the backup keys we have saved in our crypto store.
1495 pub fn get_backup_keys(&self) -> Result<Option<Arc<BackupKeys>>, CryptoStoreError> {
1496 Ok(self
1497 .runtime
1498 .block_on(self.inner.backup_machine().get_backup_keys())?
1499 .try_into()
1500 .ok()
1501 .map(Arc::new))
1502 }
1503
1504 /// Sign the given message using our device key and if available cross
1505 /// signing master key.
1506 pub fn sign(
1507 &self,
1508 message: String,
1509 ) -> Result<HashMap<String, HashMap<String, String>>, CryptoStoreError> {
1510 Ok(self
1511 .runtime
1512 .block_on(self.inner.sign(&message))?
1513 .into_iter()
1514 .map(|(k, v)| {
1515 (
1516 k.to_string(),
1517 v.into_iter()
1518 .map(|(k, v)| {
1519 (
1520 k.to_string(),
1521 match v {
1522 Ok(s) => s.to_base64(),
1523 Err(i) => i.source,
1524 },
1525 )
1526 })
1527 .collect(),
1528 )
1529 })
1530 .collect())
1531 }
1532
1533 /// Check if the given backup has been verified by us or by another of our
1534 /// devices that we trust.
1535 ///
1536 /// The `backup_info` should be a JSON encoded object with the following
1537 /// format:
1538 ///
1539 /// ```json
1540 /// {
1541 /// "algorithm": "m.megolm_backup.v1.curve25519-aes-sha2",
1542 /// "auth_data": {
1543 /// "public_key":"XjhWTCjW7l59pbfx9tlCBQolfnIQWARoKOzjTOPSlWM",
1544 /// "signatures": {}
1545 /// }
1546 /// }
1547 /// ```
1548 pub fn verify_backup(
1549 &self,
1550 backup_info: String,
1551 ) -> Result<SignatureVerification, CryptoStoreError> {
1552 let backup_info = serde_json::from_str(&backup_info)?;
1553
1554 Ok(self
1555 .runtime
1556 .block_on(self.inner.backup_machine().verify_backup(backup_info, false))?
1557 .into())
1558 }
1559
1560 /// Manage dehydrated devices.
1561 pub fn dehydrated_devices(&self) -> Arc<DehydratedDevices> {
1562 DehydratedDevices {
1563 inner: ManuallyDrop::new(self.inner.dehydrated_devices()),
1564 runtime: self.runtime.handle().to_owned(),
1565 }
1566 .into()
1567 }
1568}
1569
1570impl OlmMachine {
1571 fn import_room_keys_helper(
1572 &self,
1573 keys: Vec<ExportedRoomKey>,
1574 from_backup_version: Option<&str>,
1575 progress_listener: Box<dyn ProgressListener>,
1576 ) -> Result<KeysImportResult, KeyImportError> {
1577 let listener = |progress: usize, total: usize| {
1578 progress_listener.on_progress(progress as i32, total as i32)
1579 };
1580
1581 let result = self.runtime.block_on(self.inner.store().import_room_keys(
1582 keys,
1583 from_backup_version,
1584 listener,
1585 ))?;
1586
1587 Ok(KeysImportResult {
1588 imported: result.imported_count as i64,
1589 total: result.total_count as i64,
1590 keys: result
1591 .keys
1592 .into_iter()
1593 .map(|(r, m)| {
1594 (
1595 r.to_string(),
1596 m.into_iter().map(|(s, k)| (s, k.into_iter().collect())).collect(),
1597 )
1598 })
1599 .collect(),
1600 })
1601 }
1602}