Skip to content

Commit 57d21cc

Browse files
authored
Create a separate error variant to indicate a failure importing a secret. (#5647)
Part of the fix to element-hq/element-x-android#5099 Allows applications to distinguish between errors that occur when unlocking Secret Storage, or errors that occur when importing a secret, so that they can display appropriate feedback (or not) to the user. - [ ] Public API changes documented in changelogs (optional) <!-- Sign-off, if not part of the commits --> <!-- See CONTRIBUTING.md if you don't know what this is --> Signed-off-by: --------- Signed-off-by: Hubert Chathi <[email protected]>
1 parent 37ee5d5 commit 57d21cc

File tree

9 files changed

+545
-40
lines changed

9 files changed

+545
-40
lines changed

bindings/matrix-sdk-ffi/src/encryption.rs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,16 +79,24 @@ pub enum RecoveryError {
7979
#[error(transparent)]
8080
Client { source: crate::ClientError },
8181

82-
/// Error in the secret storage subsystem.
82+
/// Error in the secret storage subsystem, except for when importing a
83+
/// secret.
8384
#[error("Error in the secret-storage subsystem: {error_message}")]
8485
SecretStorage { error_message: String },
86+
87+
/// Error when importing a secret from secret storage.
88+
#[error("Error importing a secret: {error_message}")]
89+
Import { error_message: String },
8590
}
8691

8792
impl From<matrix_sdk::encryption::recovery::RecoveryError> for RecoveryError {
8893
fn from(value: matrix_sdk::encryption::recovery::RecoveryError) -> Self {
8994
match value {
9095
recovery::RecoveryError::BackupExistsOnServer => Self::BackupExistsOnServer,
9196
recovery::RecoveryError::Sdk(e) => Self::Client { source: ClientError::from(e) },
97+
recovery::RecoveryError::SecretStorage(
98+
matrix_sdk::encryption::secret_storage::SecretStorageError::ImportError { .. },
99+
) => Self::Import { error_message: value.to_string() },
92100
recovery::RecoveryError::SecretStorage(e) => {
93101
Self::SecretStorage { error_message: e.to_string() }
94102
}

crates/matrix-sdk-crypto/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@ All notable changes to this project will be documented in this file.
1515

1616
### Features
1717

18+
- [**breaking**] Add `name` fields to some of the variants of
19+
`store::SecretImportError` to indicate what secret was being imported when the
20+
error occurred.
21+
([#5647](https://github.com/matrix-org/matrix-rust-sdk/pull/5647))
1822
- Log message index for Megolm sessions received over encrypted to-device messages. ([#5599](https://github.com/matrix-org/matrix-rust-sdk/pull/5599))
1923
- Add `RoomSettings::encrypt_state_events` flag. ([#5511](https://github.com/matrix-org/matrix-rust-sdk/pull/5511))
2024
- Make sure to accept historic room key bundles only if the sender is trusted

crates/matrix-sdk-crypto/src/olm/signing/mod.rs

Lines changed: 37 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -235,36 +235,53 @@ impl PrivateCrossSigningIdentity {
235235
user_signing_key: Option<&str>,
236236
) -> Result<(), SecretImportError> {
237237
let master = if let Some(master_key) = master_key {
238-
let master = MasterSigning::from_base64(self.user_id().to_owned(), master_key)?;
238+
let master =
239+
MasterSigning::from_base64(self.user_id().to_owned(), master_key).map_err(|e| {
240+
SecretImportError::Key { name: SecretName::CrossSigningMasterKey, error: e }
241+
})?;
239242

240243
if public_identity.master_key() == master.public_key() {
241244
Some(master)
242245
} else {
243-
return Err(SecretImportError::MismatchedPublicKeys);
246+
return Err(SecretImportError::MismatchedPublicKeys {
247+
name: SecretName::CrossSigningMasterKey,
248+
});
244249
}
245250
} else {
246251
None
247252
};
248253

249254
let user_signing = if let Some(user_signing_key) = user_signing_key {
250-
let subkey = UserSigning::from_base64(self.user_id().to_owned(), user_signing_key)?;
255+
let subkey = UserSigning::from_base64(self.user_id().to_owned(), user_signing_key)
256+
.map_err(|e| SecretImportError::Key {
257+
name: SecretName::CrossSigningUserSigningKey,
258+
error: e,
259+
})?;
251260

252261
if public_identity.user_signing_key() == subkey.public_key() {
253262
Ok(Some(subkey))
254263
} else {
255-
Err(SecretImportError::MismatchedPublicKeys)
264+
Err(SecretImportError::MismatchedPublicKeys {
265+
name: SecretName::CrossSigningUserSigningKey,
266+
})
256267
}
257268
} else {
258269
Ok(None)
259270
}?;
260271

261272
let self_signing = if let Some(self_signing_key) = self_signing_key {
262-
let subkey = SelfSigning::from_base64(self.user_id().to_owned(), self_signing_key)?;
273+
let subkey = SelfSigning::from_base64(self.user_id().to_owned(), self_signing_key)
274+
.map_err(|e| SecretImportError::Key {
275+
name: SecretName::CrossSigningSelfSigningKey,
276+
error: e,
277+
})?;
263278

264279
if public_identity.self_signing_key() == subkey.public_key() {
265280
Ok(Some(subkey))
266281
} else {
267-
Err(SecretImportError::MismatchedPublicKeys)
282+
Err(SecretImportError::MismatchedPublicKeys {
283+
name: SecretName::CrossSigningSelfSigningKey,
284+
})
268285
}
269286
} else {
270287
Ok(None)
@@ -299,17 +316,28 @@ impl PrivateCrossSigningIdentity {
299316
user_signing_key: Option<&str>,
300317
) -> Result<(), SecretImportError> {
301318
if let Some(master_key) = master_key {
302-
let master = MasterSigning::from_base64(self.user_id().to_owned(), master_key)?;
319+
let master =
320+
MasterSigning::from_base64(self.user_id().to_owned(), master_key).map_err(|e| {
321+
SecretImportError::Key { name: SecretName::CrossSigningMasterKey, error: e }
322+
})?;
303323
*self.master_key.lock().await = Some(master);
304324
}
305325

306326
if let Some(user_signing_key) = user_signing_key {
307-
let subkey = UserSigning::from_base64(self.user_id().to_owned(), user_signing_key)?;
327+
let subkey = UserSigning::from_base64(self.user_id().to_owned(), user_signing_key)
328+
.map_err(|e| SecretImportError::Key {
329+
name: SecretName::CrossSigningUserSigningKey,
330+
error: e,
331+
})?;
308332
*self.user_signing_key.lock().await = Some(subkey);
309333
}
310334

311335
if let Some(self_signing_key) = self_signing_key {
312-
let subkey = SelfSigning::from_base64(self.user_id().to_owned(), self_signing_key)?;
336+
let subkey = SelfSigning::from_base64(self.user_id().to_owned(), self_signing_key)
337+
.map_err(|e| SecretImportError::Key {
338+
name: SecretName::CrossSigningSelfSigningKey,
339+
error: e,
340+
})?;
313341
*self.self_signing_key.lock().await = Some(subkey);
314342
}
315343

crates/matrix-sdk-crypto/src/store/mod.rs

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -492,15 +492,23 @@ struct StoreInner {
492492
#[derive(Debug, Error)]
493493
pub enum SecretImportError {
494494
/// The key that we tried to import was invalid.
495-
#[error(transparent)]
496-
Key(#[from] vodozemac::KeyError),
497-
/// The public key of the imported private key doesn't match to the public
495+
#[error("Error while importing {name}: {error}")]
496+
Key {
497+
/// The name of the secret that was being imported.
498+
name: SecretName,
499+
/// The key error that occurred.
500+
error: vodozemac::KeyError,
501+
},
502+
/// The public key of the imported private key doesn't match the public
498503
/// key that was uploaded to the server.
499504
#[error(
500-
"The public key of the imported private key doesn't match to the \
501-
public key that was uploaded to the server"
505+
"Error while importing {name}: The public key of the imported private \
506+
key doesn't match the public key that was uploaded to the server"
502507
)]
503-
MismatchedPublicKeys,
508+
MismatchedPublicKeys {
509+
/// The name of the secret that was being imported.
510+
name: SecretName,
511+
},
504512
/// The new version of the identity couldn't be stored.
505513
#[error(transparent)]
506514
Store(#[from] CryptoStoreError),

crates/matrix-sdk/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@ All notable changes to this project will be documented in this file.
88

99
### Features
1010

11+
- [**breaking**] Add `encryption::secret_storage::SecretStorageError::ImportError` to indicate
12+
an error that occurred when importing a secret from secret storage.
13+
([#5647](https://github.com/matrix-org/matrix-rust-sdk/pull/5647))
14+
1115
### Refactor
1216
- The Matrix SDK crate now uses the 2024 edition of Rust.
1317
([#5677](https://github.com/matrix-org/matrix-rust-sdk/pull/5677))

crates/matrix-sdk/src/encryption/recovery/mod.rs

Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -728,3 +728,173 @@ impl IdentityResetHandle {
728728
self.cross_signing_reset_handle.cancel().await;
729729
}
730730
}
731+
732+
// The http mocking library is not supported for wasm32
733+
#[cfg(all(test, not(target_family = "wasm")))]
734+
pub(crate) mod tests {
735+
use assert_matches::assert_matches;
736+
use matrix_sdk_test::async_test;
737+
use ruma::{
738+
events::{secret::request::SecretName, secret_storage::key},
739+
serde::Base64,
740+
};
741+
use serde_json::json;
742+
743+
use super::Recovery;
744+
use crate::{
745+
encryption::{recovery::types::RecoveryError, secret_storage::SecretStorageError},
746+
test_utils::mocks::MatrixMockServer,
747+
};
748+
749+
// If recovery fails due when importing a secret from secret storage, we
750+
// should get the `ImportError` variant of `SecretStorageError`. The
751+
// following tests test different import failures.
752+
#[async_test]
753+
async fn test_recover_with_no_cross_signing_key() {
754+
let server = MatrixMockServer::new().await;
755+
let client = server.client_builder().build().await;
756+
757+
server
758+
.mock_get_secret_storage_key()
759+
.ok(
760+
client.user_id().unwrap(),
761+
&key::SecretStorageKeyEventContent::new(
762+
"abc".into(),
763+
key::SecretStorageEncryptionAlgorithm::V1AesHmacSha2(
764+
key::SecretStorageV1AesHmacSha2Properties::new(
765+
Some(Base64::parse("xv5b6/p3ExEw++wTyfSHEg==").unwrap()),
766+
Some(
767+
Base64::parse("ujBBbXahnTAMkmPUX2/0+VTfUh63pGyVRuBcDMgmJC8=")
768+
.unwrap(),
769+
),
770+
),
771+
),
772+
),
773+
)
774+
.mount()
775+
.await;
776+
server
777+
.mock_get_default_secret_storage_key()
778+
.ok(client.user_id().unwrap(), "abc")
779+
.mount()
780+
.await;
781+
782+
let recovery = Recovery { client };
783+
784+
let ret =
785+
recovery.recover("EsTj 3yST y93F SLpB jJsz eAXc 2XzA ygD3 w69H fGaN TKBj jXEd").await;
786+
787+
assert_matches!(
788+
ret,
789+
Err(RecoveryError::SecretStorage(SecretStorageError::ImportError {
790+
name: SecretName::CrossSigningMasterKey,
791+
error: _
792+
}))
793+
);
794+
}
795+
796+
#[async_test]
797+
async fn test_recover_with_invalid_cross_signing_key() {
798+
let server = MatrixMockServer::new().await;
799+
let client = server.client_builder().build().await;
800+
801+
server
802+
.mock_get_secret_storage_key()
803+
.ok(
804+
client.user_id().unwrap(),
805+
&key::SecretStorageKeyEventContent::new(
806+
"abc".into(),
807+
key::SecretStorageEncryptionAlgorithm::V1AesHmacSha2(
808+
key::SecretStorageV1AesHmacSha2Properties::new(
809+
Some(Base64::parse("xv5b6/p3ExEw++wTyfSHEg==").unwrap()),
810+
Some(
811+
Base64::parse("ujBBbXahnTAMkmPUX2/0+VTfUh63pGyVRuBcDMgmJC8=")
812+
.unwrap(),
813+
),
814+
),
815+
),
816+
),
817+
)
818+
.mount()
819+
.await;
820+
server
821+
.mock_get_default_secret_storage_key()
822+
.ok(client.user_id().unwrap(), "abc")
823+
.mount()
824+
.await;
825+
server.mock_get_master_signing_key().ok(client.user_id().unwrap(), json!({})).mount().await;
826+
827+
let recovery = Recovery { client };
828+
829+
let ret =
830+
recovery.recover("EsTj 3yST y93F SLpB jJsz eAXc 2XzA ygD3 w69H fGaN TKBj jXEd").await;
831+
832+
assert_matches!(
833+
ret,
834+
Err(RecoveryError::SecretStorage(SecretStorageError::ImportError {
835+
name: SecretName::CrossSigningMasterKey,
836+
error: _
837+
}))
838+
);
839+
}
840+
841+
#[async_test]
842+
async fn test_recover_with_undecryptable_cross_signing_key() {
843+
let server = MatrixMockServer::new().await;
844+
let client = server.client_builder().build().await;
845+
846+
server
847+
.mock_get_secret_storage_key()
848+
.ok(
849+
client.user_id().unwrap(),
850+
&key::SecretStorageKeyEventContent::new(
851+
"abc".into(),
852+
key::SecretStorageEncryptionAlgorithm::V1AesHmacSha2(
853+
key::SecretStorageV1AesHmacSha2Properties::new(
854+
Some(Base64::parse("xv5b6/p3ExEw++wTyfSHEg==").unwrap()),
855+
Some(
856+
Base64::parse("ujBBbXahnTAMkmPUX2/0+VTfUh63pGyVRuBcDMgmJC8=")
857+
.unwrap(),
858+
),
859+
),
860+
),
861+
),
862+
)
863+
.mount()
864+
.await;
865+
server
866+
.mock_get_default_secret_storage_key()
867+
.ok(client.user_id().unwrap(), "abc")
868+
.mount()
869+
.await;
870+
server
871+
.mock_get_master_signing_key()
872+
.ok(
873+
client.user_id().unwrap(),
874+
json!({
875+
"encrypted": {
876+
"abc": {
877+
"iv": "xv5b6/p3ExEw++wTyfSHEg==",
878+
"mac": "ujBBbXahnTAMkmPUX2/0+VTfUh63pGyVRuBcDMgmJC8=",
879+
"ciphertext": "abcd"
880+
}
881+
}
882+
}),
883+
)
884+
.mount()
885+
.await;
886+
887+
let recovery = Recovery { client };
888+
889+
let ret =
890+
recovery.recover("EsTj 3yST y93F SLpB jJsz eAXc 2XzA ygD3 w69H fGaN TKBj jXEd").await;
891+
892+
assert_matches!(
893+
ret,
894+
Err(RecoveryError::SecretStorage(SecretStorageError::ImportError {
895+
name: SecretName::CrossSigningMasterKey,
896+
error: _
897+
}))
898+
);
899+
}
900+
}

0 commit comments

Comments
 (0)