diff --git a/matrix_sdk_crypto/src/lib.rs b/matrix_sdk_crypto/src/lib.rs index a598ddbe..9704a002 100644 --- a/matrix_sdk_crypto/src/lib.rs +++ b/matrix_sdk_crypto/src/lib.rs @@ -56,7 +56,7 @@ pub use requests::{ IncomingResponse, KeysQueryRequest, OutgoingRequest, OutgoingRequests, OutgoingVerificationRequest, RoomMessageRequest, ToDeviceRequest, }; -pub use store::CryptoStoreError; +pub use store::{CrossSigningKeyExport, CryptoStoreError, SecretImportError}; pub use verification::{ AcceptSettings, CancelInfo, QrVerification, Sas, Verification, VerificationRequest, }; diff --git a/matrix_sdk_crypto/src/machine.rs b/matrix_sdk_crypto/src/machine.rs index 78780053..df32184e 100644 --- a/matrix_sdk_crypto/src/machine.rs +++ b/matrix_sdk_crypto/src/machine.rs @@ -38,6 +38,7 @@ use ruma::{ EncryptedEventContent, EncryptedEventScheme, EncryptedToDeviceEventContent, }, room_key::RoomKeyToDeviceEventContent, + secret::request::SecretName, AnyMessageEventContent, AnyRoomEvent, AnyToDeviceEvent, SyncMessageEvent, ToDeviceEvent, }, DeviceId, DeviceIdBox, DeviceKeyAlgorithm, EventEncryptionAlgorithm, RoomId, UInt, UserId, @@ -59,10 +60,10 @@ use crate::{ session_manager::{GroupSessionManager, SessionManager}, store::{ Changes, CryptoStore, DeviceChanges, IdentityChanges, MemoryStore, Result as StoreResult, - Store, + SecretImportError, Store, }, verification::{Verification, VerificationMachine, VerificationRequest}, - ToDeviceRequest, + CrossSigningKeyExport, ToDeviceRequest, }; /// State machine implementation of the Olm/Megolm encryption protocol used for @@ -1262,6 +1263,38 @@ impl OlmMachine { pub async fn cross_signing_status(&self) -> CrossSigningStatus { self.user_identity.lock().await.status().await } + + /// Export all the private cross signing keys we have. + /// + /// The export will contain the seed for the ed25519 keys as a unpadded + /// base64 encoded string. + /// + /// This method returns `None` if we don't have any private cross signing + /// keys. + pub async fn export_cross_signing_keys(&self) -> Option { + let master_key = self.store.export_secret(&SecretName::CrossSigningMasterKey).await; + let self_signing_key = + self.store.export_secret(&SecretName::CrossSigningSelfSigningKey).await; + let user_signing_key = + self.store.export_secret(&SecretName::CrossSigningUserSigningKey).await; + + if master_key.is_none() && self_signing_key.is_none() && user_signing_key.is_none() { + None + } else { + Some(CrossSigningKeyExport { master_key, self_signing_key, user_signing_key }) + } + } + + /// Import our private cross signing keys. + /// + /// The export needs to contain the seed for the ed25519 keys as an unpadded + /// base64 encoded string. + pub async fn import_cross_signing_keys( + &self, + export: CrossSigningKeyExport, + ) -> Result { + self.store.import_cross_signing_keys(export).await + } } #[cfg(test)] diff --git a/matrix_sdk_crypto/src/olm/signing/mod.rs b/matrix_sdk_crypto/src/olm/signing/mod.rs index 3850d5c2..77ae6b25 100644 --- a/matrix_sdk_crypto/src/olm/signing/mod.rs +++ b/matrix_sdk_crypto/src/olm/signing/mod.rs @@ -164,43 +164,81 @@ impl PrivateCrossSigningIdentity { &self, public_identity: OwnUserIdentity, secret_name: &SecretName, - seed: String, + seed: &str, ) -> Result<(), SecretImportError> { - let seed = decode(seed)?; + let (master, self_signing, user_signing) = match secret_name { + SecretName::CrossSigningMasterKey => (Some(seed), None, None), + SecretName::CrossSigningSelfSigningKey => (None, Some(seed), None), + SecretName::CrossSigningUserSigningKey => (None, None, Some(seed)), + _ => return Ok(()), + }; - match secret_name { - SecretName::CrossSigningMasterKey => { - let master = MasterSigning::from_seed(self.user_id().clone(), seed); + self.import_secrets(public_identity, master, self_signing, user_signing).await + } - if public_identity.master_key() == &master.public_key { - *self.master_key.lock().await = Some(master); - Ok(()) - } else { - Err(SecretImportError::MissmatchedPublicKeys) - } + pub(crate) async fn import_secrets( + &self, + public_identity: OwnUserIdentity, + master_key: Option<&str>, + self_signing_key: Option<&str>, + user_signing_key: Option<&str>, + ) -> Result<(), SecretImportError> { + let master = if let Some(master_key) = master_key { + let seed = decode(master_key)?; + + let master = MasterSigning::from_seed(self.user_id().clone(), seed); + + if public_identity.master_key() == &master.public_key { + Ok(Some(master)) + // *self.master_key.lock().await = Some(master); + } else { + Err(SecretImportError::MissmatchedPublicKeys) } - SecretName::CrossSigningUserSigningKey => { - let subkey = UserSigning::from_seed(self.user_id().clone(), seed); + } else { + Ok(None) + }?; - if public_identity.user_signing_key() == &subkey.public_key { - *self.user_signing_key.lock().await = Some(subkey); - Ok(()) - } else { - Err(SecretImportError::MissmatchedPublicKeys) - } - } - SecretName::CrossSigningSelfSigningKey => { - let subkey = SelfSigning::from_seed(self.user_id().clone(), seed); + let user_signing = if let Some(user_signing_key) = user_signing_key { + let seed = decode(user_signing_key)?; + let subkey = UserSigning::from_seed(self.user_id().clone(), seed); - if public_identity.self_signing_key() == &subkey.public_key { - *self.self_signing_key.lock().await = Some(subkey); - Ok(()) - } else { - Err(SecretImportError::MissmatchedPublicKeys) - } + if public_identity.user_signing_key() == &subkey.public_key { + // *self.user_signing_key.lock().await = Some(subkey); + Ok(Some(subkey)) + } else { + Err(SecretImportError::MissmatchedPublicKeys) } - _ => Ok(()), + } else { + Ok(None) + }?; + + let self_signing = if let Some(self_signing_key) = self_signing_key { + let seed = decode(self_signing_key)?; + let subkey = SelfSigning::from_seed(self.user_id().clone(), seed); + + if public_identity.self_signing_key() == &subkey.public_key { + // *self.self_signing_key.lock().await = Some(subkey); + Ok(Some(subkey)) + } else { + Err(SecretImportError::MissmatchedPublicKeys) + } + } else { + Ok(None) + }?; + + if let Some(master) = master { + *self.master_key.lock().await = Some(master); } + + if let Some(self_signing) = self_signing { + *self.self_signing_key.lock().await = Some(self_signing); + } + + if let Some(user_signing) = user_signing { + *self.user_signing_key.lock().await = Some(user_signing); + } + + Ok(()) } /// Get the names of the secrets we are missing. diff --git a/matrix_sdk_crypto/src/store/mod.rs b/matrix_sdk_crypto/src/store/mod.rs index 819f1e79..bb93e1a0 100644 --- a/matrix_sdk_crypto/src/store/mod.rs +++ b/matrix_sdk_crypto/src/store/mod.rs @@ -63,6 +63,7 @@ use ruma::{ use serde_json::Error as SerdeError; use thiserror::Error; use tracing::{info, warn}; +use zeroize::Zeroize; #[cfg(feature = "sled_cryptostore")] pub use self::sled::SledStore; @@ -136,8 +137,33 @@ impl DeviceChanges { } } +/// A struct containing private cross signing keys that can be backed up or +/// uploaded to the secret store. +#[derive(Zeroize)] +#[zeroize(drop)] +pub struct CrossSigningKeyExport { + /// The seed of the master key encoded as unpadded base64. + pub master_key: Option, + /// The seed of the self signing key encoded as unpadded base64. + pub self_signing_key: Option, + /// The seed of the user signing key encoded as unpadded base64. + pub user_signing_key: Option, +} + +impl Debug for CrossSigningKeyExport { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("CrossSigningKeyExport") + .field("master_key", &self.master_key.is_some()) + .field("self_signing_key", &self.self_signing_key.is_some()) + .field("user_signing_key", &self.user_signing_key.is_some()) + .finish_non_exhaustive() + } +} + +/// Error describing what went wrong when importing private cross signing keys +/// or the key backup key. #[derive(Debug, Error)] -pub(crate) enum SecretImportError { +pub enum SecretImportError { /// The seed for the private key wasn't valid base64. #[error(transparent)] Base64(#[from] DecodeError), @@ -367,11 +393,42 @@ impl Store { } } + pub async fn import_cross_signing_keys( + &self, + export: CrossSigningKeyExport, + ) -> Result { + if let Some(public_identity) = self.get_identity(&self.user_id).await?.and_then(|i| i.own()) + { + let identity = self.identity.lock().await; + + identity + .import_secrets( + public_identity, + export.master_key.as_deref(), + export.self_signing_key.as_deref(), + export.user_signing_key.as_deref(), + ) + .await?; + + let status = identity.status().await; + info!(status =? status, "Successfully imported the private cross signing keys"); + + let changes = + Changes { private_identity: Some(identity.clone()), ..Default::default() }; + + self.save_changes(changes).await?; + } + + Ok(self.identity.lock().await.status().await) + } + pub async fn import_secret( &self, secret_name: &SecretName, secret: String, ) -> Result<(), SecretImportError> { + let secret = zeroize::Zeroizing::new(secret); + match secret_name { SecretName::CrossSigningMasterKey | SecretName::CrossSigningUserSigningKey @@ -381,7 +438,7 @@ impl Store { { let identity = self.identity.lock().await; - identity.import_secret(public_identity, secret_name, secret).await?; + identity.import_secret(public_identity, secret_name, &secret).await?; info!( secret_name = secret_name.as_ref(), "Successfully imported a private cross signing key"