From 68df9b6ed2032a6bef6ce9f072a1701643eca485 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Mon, 2 Aug 2021 08:03:08 +0200 Subject: [PATCH] crypto: Allow secrets to be requested and imported --- matrix_sdk_crypto/src/key_request.rs | 132 +++++++++++++++--- matrix_sdk_crypto/src/machine.rs | 4 + matrix_sdk_crypto/src/olm/signing/mod.rs | 46 +++++- .../src/olm/signing/pk_signing.rs | 21 +++ matrix_sdk_crypto/src/store/memorystore.rs | 6 +- matrix_sdk_crypto/src/store/mod.rs | 56 +++++++- matrix_sdk_crypto/src/store/sled.rs | 8 +- matrix_sdk_crypto/src/verification/mod.rs | 17 ++- 8 files changed, 249 insertions(+), 41 deletions(-) diff --git a/matrix_sdk_crypto/src/key_request.rs b/matrix_sdk_crypto/src/key_request.rs index db369253..f3b1be05 100644 --- a/matrix_sdk_crypto/src/key_request.rs +++ b/matrix_sdk_crypto/src/key_request.rs @@ -48,7 +48,7 @@ use crate::{ olm::{InboundGroupSession, Session, ShareState}, requests::{OutgoingRequest, ToDeviceRequest}, session_manager::GroupSessionCache, - store::{Changes, CryptoStoreError, Store}, + store::{Changes, CryptoStoreError, SecretImportError, Store}, Device, }; @@ -230,6 +230,16 @@ impl From for SecretInfo { } impl OutgoingKeyRequest { + /// Create an ougoing secret request for the given secret. + pub(crate) fn from_secret_name(own_user_id: UserId, secret_name: SecretName) -> Self { + Self { + request_recipient: own_user_id, + request_id: Uuid::new_v4(), + info: secret_name.into(), + sent_out: false, + } + } + fn request_type(&self) -> &str { match &self.info { SecretInfo::KeyRequest(_) => "m.room_key_request", @@ -815,34 +825,22 @@ impl KeyRequestMachine { } } - #[allow(dead_code)] - pub async fn request_missing_secrets(&self) -> Result, CryptoStoreError> { - let secret_names = self.store.get_missing_secrets().await; - - Ok(if secret_names.is_empty() { + /// Create outgoing secret requests for the given + pub fn request_missing_secrets( + own_user_id: &UserId, + secret_names: Vec, + ) -> Vec { + if !secret_names.is_empty() { info!(secret_names =? secret_names, "Creating new outgoing secret requests"); - let requests: Vec = secret_names + secret_names .into_iter() - .map(|n| OutgoingKeyRequest { - request_recipient: self.user_id().to_owned(), - request_id: Uuid::new_v4(), - info: n.into(), - sent_out: false, - }) - .collect(); - - let outgoing_requests = - requests.iter().map(|r| r.to_request(self.device_id())).collect(); - - let changes = Changes { key_requests: requests, ..Default::default() }; - self.store.save_changes(changes).await?; - - outgoing_requests + .map(|n| OutgoingKeyRequest::from_secret_name(own_user_id.to_owned(), n)) + .collect() } else { trace!("No secrets are missing from our store, not requesting them"); vec![] - }) + } } async fn request_key_helper( @@ -972,6 +970,94 @@ impl KeyRequestMachine { Ok(()) } + pub async fn receive_secret( + &self, + sender_key: &str, + event: &mut ToDeviceEvent, + ) -> Result, CryptoStoreError> { + debug!( + sender = event.sender.as_str(), + request_id = event.content.request_id.as_str(), + "Received a m.secret.send event" + ); + + let request_id = if let Ok(r) = Uuid::parse_str(&event.content.request_id) { + r + } else { + warn!("Received a m.secret.send event but the request ID is invalid"); + return Ok(None); + }; + + if let Some(request) = self.store.get_outgoing_secret_requests(request_id).await? { + match &request.info { + SecretInfo::KeyRequest(_) => { + warn!( + sender = event.sender.as_str(), + request_id = event.content.request_id.as_str(), + "Received a m.secret.send event but the request was for a room key" + ); + } + SecretInfo::SecretRequest(secret_name) => { + debug!( + sender = event.sender.as_str(), + request_id = event.content.request_id.as_str(), + secret_name = secret_name.as_ref(), + "Received a m.secret.send event with a matching request" + ); + + if let Some(device) = + self.store.get_device_from_curve_key(&event.sender, sender_key).await? + { + if device.verified() { + match self + .store + .import_secret( + &secret_name, + std::mem::take(&mut event.content.secret), + ) + .await + { + Ok(_) => self.mark_as_done(request).await?, + Err(e) => { + // If this is a store error propagate it up + // the call stack. + if let SecretImportError::Store(e) = e { + return Err(e); + } else { + // Otherwise warn that there was + // something wrong with the secret. + warn!( + secret_name = secret_name.as_ref(), + error =? e, + "Error while importing a secret" + ) + } + } + } + } else { + warn!( + sender = event.sender.as_str(), + request_id = event.content.request_id.as_str(), + secret_name = secret_name.as_ref(), + "Received a m.secret.send event from an unverified device" + ); + } + } else { + warn!( + sender = event.sender.as_str(), + request_id = event.content.request_id.as_str(), + secret_name = secret_name.as_ref(), + "Received a m.secret.send event from an unknown device" + ); + self.store.update_tracked_user(&event.sender, true).await?; + } + } + } + } + + Ok(Some(AnyToDeviceEvent::SecretSend(event.clone()))) + } + /// Receive a forwarded room key event. pub async fn receive_forwarded_room_key( &self, diff --git a/matrix_sdk_crypto/src/machine.rs b/matrix_sdk_crypto/src/machine.rs index 0617a4ba..0a7bda11 100644 --- a/matrix_sdk_crypto/src/machine.rs +++ b/matrix_sdk_crypto/src/machine.rs @@ -706,6 +706,10 @@ impl OlmMachine { .key_request_machine .receive_forwarded_room_key(&decrypted.sender_key, &mut e) .await?), + AnyToDeviceEvent::SecretSend(mut e) => Ok(( + self.key_request_machine.receive_secret(&decrypted.sender_key, &mut e).await?, + None, + )), _ => { warn!(event_type =? event.event_type(), "Received an unexpected encrypted to-device event"); Ok((Some(event), None)) diff --git a/matrix_sdk_crypto/src/olm/signing/mod.rs b/matrix_sdk_crypto/src/olm/signing/mod.rs index 579f0a73..abf8c9d8 100644 --- a/matrix_sdk_crypto/src/olm/signing/mod.rs +++ b/matrix_sdk_crypto/src/olm/signing/mod.rs @@ -35,7 +35,8 @@ use serde_json::Error as JsonError; use crate::{ error::SignatureError, identities::MasterPubkey, requests::UploadSigningKeysRequest, - ReadOnlyAccount, ReadOnlyDevice, ReadOnlyOwnUserIdentity, ReadOnlyUserIdentity, + store::SecretImportError, utilities::decode, OwnUserIdentity, ReadOnlyAccount, ReadOnlyDevice, + ReadOnlyOwnUserIdentity, ReadOnlyUserIdentity, }; /// Private cross signing identity. @@ -135,6 +136,49 @@ impl PrivateCrossSigningIdentity { } } + pub(crate) async fn import_secret( + &self, + public_identity: OwnUserIdentity, + secret_name: &SecretName, + seed: String, + ) -> Result<(), SecretImportError> { + let seed = decode(seed)?; + + match secret_name { + SecretName::CrossSigningMasterKey => { + let master = MasterSigning::from_seed(self.user_id().clone(), seed); + + if public_identity.master_key() == &master.public_key { + *self.master_key.lock().await = Some(master); + Ok(()) + } else { + Err(SecretImportError::MissmatchedPublicKeys) + } + } + SecretName::CrossSigningUserSigningKey => { + let subkey = UserSigning::from_seed(self.user_id().clone(), seed); + + 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); + + if public_identity.self_signing_key() == &subkey.public_key { + *self.self_signing_key.lock().await = Some(subkey); + Ok(()) + } else { + Err(SecretImportError::MissmatchedPublicKeys) + } + } + _ => Ok(()), + } + } + /// Get the names of the secrets we are missing. pub(crate) async fn get_missing_secrets(&self) -> Vec { let mut missing = Vec::new(); diff --git a/matrix_sdk_crypto/src/olm/signing/pk_signing.rs b/matrix_sdk_crypto/src/olm/signing/pk_signing.rs index cc438766..15d94454 100644 --- a/matrix_sdk_crypto/src/olm/signing/pk_signing.rs +++ b/matrix_sdk_crypto/src/olm/signing/pk_signing.rs @@ -146,6 +146,13 @@ impl MasterSigning { encode(self.inner.seed.as_slice()) } + pub fn from_seed(user_id: UserId, seed: Vec) -> Self { + let inner = Signing::from_seed(seed); + let public_key = inner.cross_signing_key(user_id, KeyUsage::Master).into(); + + Self { inner, public_key } + } + pub fn from_pickle( pickle: PickledMasterSigning, pickle_key: &[u8], @@ -192,6 +199,13 @@ impl UserSigning { encode(self.inner.seed.as_slice()) } + pub fn from_seed(user_id: UserId, seed: Vec) -> Self { + let inner = Signing::from_seed(seed); + let public_key = inner.cross_signing_key(user_id, KeyUsage::UserSigning).into(); + + Self { inner, public_key } + } + pub async fn sign_user( &self, user: &ReadOnlyUserIdentity, @@ -237,6 +251,13 @@ impl SelfSigning { encode(self.inner.seed.as_slice()) } + pub fn from_seed(user_id: UserId, seed: Vec) -> Self { + let inner = Signing::from_seed(seed); + let public_key = inner.cross_signing_key(user_id, KeyUsage::SelfSigning).into(); + + Self { inner, public_key } + } + pub async fn sign_device_helper(&self, value: Value) -> Result { self.inner.sign_json(value).await } diff --git a/matrix_sdk_crypto/src/store/memorystore.rs b/matrix_sdk_crypto/src/store/memorystore.rs index 549f5f1c..7b5ea39e 100644 --- a/matrix_sdk_crypto/src/store/memorystore.rs +++ b/matrix_sdk_crypto/src/store/memorystore.rs @@ -36,11 +36,7 @@ fn encode_key_info(info: &SecretInfo) -> String { SecretInfo::KeyRequest(info) => { format!("{}{}{}{}", info.room_id, info.sender_key, info.algorithm, info.session_id) } - SecretInfo::SecretRequest(i) => { - // TODO don't use serde here, use `as_ref()` when it becomes - // available - serde_json::to_string(i).expect("Can't serialize secret name") - } + SecretInfo::SecretRequest(i) => i.as_ref().to_owned(), } } diff --git a/matrix_sdk_crypto/src/store/mod.rs b/matrix_sdk_crypto/src/store/mod.rs index 56175f6e..5fbd0e8d 100644 --- a/matrix_sdk_crypto/src/store/mod.rs +++ b/matrix_sdk_crypto/src/store/mod.rs @@ -51,6 +51,7 @@ use std::{ sync::Arc, }; +use base64::DecodeError; use matrix_sdk_common::{async_trait, locks::Mutex, uuid::Uuid, AsyncTraitDeps}; pub use memorystore::MemoryStore; use olm_rs::errors::{OlmAccountError, OlmGroupSessionError, OlmSessionError}; @@ -61,7 +62,7 @@ use ruma::{ }; use serde_json::Error as SerdeError; use thiserror::Error; -use tracing::warn; +use tracing::{info, warn}; #[cfg(feature = "sled_cryptostore")] pub use self::sled::SledStore; @@ -134,6 +135,23 @@ impl DeviceChanges { } } +#[derive(Debug, Error)] +pub(crate) enum SecretImportError { + /// The seed for the private key wasn't valid base64. + #[error(transparent)] + Base64(#[from] DecodeError), + /// The public key of the imported private key doesn't match to the public + /// key that was uploaded to the server. + #[error( + "The public key of the imported private key doesn't match to the \ + public key that was uploaded to the server" + )] + MissmatchedPublicKeys, + /// The new version of the identity couldn't be stored. + #[error(transparent)] + Store(#[from] CryptoStoreError), +} + impl Store { pub fn new( user_id: Arc, @@ -273,9 +291,39 @@ impl Store { } } - pub async fn get_missing_secrets(&self) -> Vec { - // TODO add the backup key to our missing secrets - self.identity.lock().await.get_missing_secrets().await + pub async fn import_secret( + &self, + secret_name: &SecretName, + secret: String, + ) -> Result<(), SecretImportError> { + match secret_name { + SecretName::CrossSigningMasterKey + | SecretName::CrossSigningUserSigningKey + | SecretName::CrossSigningSelfSigningKey => { + 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_secret(public_identity, secret_name, secret).await?; + info!( + secret_name = secret_name.as_ref(), + "Successfully imported a private cross signing key" + ); + + let mut changes = Changes::default(); + changes.private_identity = Some(identity.clone()); + + self.save_changes(changes).await?; + } + } + SecretName::RecoveryKey => (), + name => { + warn!(secret =? name, "Tried to import an unknown secret"); + } + } + + Ok(()) } } diff --git a/matrix_sdk_crypto/src/store/sled.rs b/matrix_sdk_crypto/src/store/sled.rs index c1c7dd0f..4764af53 100644 --- a/matrix_sdk_crypto/src/store/sled.rs +++ b/matrix_sdk_crypto/src/store/sled.rs @@ -60,13 +60,7 @@ impl EncodeKey for Uuid { impl EncodeKey for SecretName { fn encode(&self) -> Vec { - [ - // TODO don't use serde here, use `as_ref()` when it becomes - // available - serde_json::to_string(self).expect("Can't serialize secret name").as_bytes(), - &[Self::SEPARATOR], - ] - .concat() + [self.as_ref().as_bytes(), &[Self::SEPARATOR]].concat() } } diff --git a/matrix_sdk_crypto/src/verification/mod.rs b/matrix_sdk_crypto/src/verification/mod.rs index ee89552d..2da10725 100644 --- a/matrix_sdk_crypto/src/verification/mod.rs +++ b/matrix_sdk_crypto/src/verification/mod.rs @@ -42,6 +42,7 @@ use tracing::{error, info, trace, warn}; use crate::{ error::SignatureError, + key_request::{KeyRequestMachine, OutgoingKeyRequest}, olm::PrivateCrossSigningIdentity, store::{Changes, CryptoStore}, CryptoStoreError, LocalTrust, ReadOnlyDevice, ReadOnlyUserIdentities, @@ -351,6 +352,10 @@ impl IdentitiesBeingVerified { return Ok(VerificationResult::Cancel(CancelCode::KeyMismatch)); } + let is_self_verification = + device.as_ref().map(|d| d.user_id() == self.user_id()).unwrap_or_default() + || identity.as_ref().map(|i| i.own().is_some()).unwrap_or_default(); + let mut changes = Changes::default(); let signature_request = if let Some(device) = device { @@ -361,7 +366,7 @@ impl IdentitiesBeingVerified { Err(SignatureError::MissingSigningKey) => { warn!( "Can't sign the device keys for {} {}, \ - no private user signing key found", + no private device signing key found", device.user_id(), device.device_id(), ); @@ -437,6 +442,11 @@ impl IdentitiesBeingVerified { identity_signature_request }; + if is_self_verification { + let secret_requests = self.request_missing_secrets().await; + changes.key_requests = secret_requests; + } + // TODO store the signature upload request as well. self.store.save_changes(changes).await?; @@ -445,6 +455,11 @@ impl IdentitiesBeingVerified { .unwrap_or(VerificationResult::Ok)) } + async fn request_missing_secrets(&self) -> Vec { + let secrets = self.private_identity.get_missing_secrets().await; + KeyRequestMachine::request_missing_secrets(self.user_id(), secrets) + } + async fn mark_identity_as_verified( &self, verified_identities: Option<&[ReadOnlyUserIdentities]>,