Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

crypto: Authenticated backup #2659

Closed
wants to merge 19 commits into from
Closed
Show file tree
Hide file tree
Changes from 15 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 11 additions & 18 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,9 @@ futures-executor = "0.3.21"
futures-util = { version = "0.3.26", default-features = false, features = ["alloc"] }
http = "0.2.6"
itertools = "0.11.0"
ruma = { version = "0.9.2", features = ["client-api-c", "compat-upload-signatures", "compat-user-id", "compat-arbitrary-length-ids", "unstable-msc3401"] }
ruma-common = "0.12.0"
ruma = { git = "https://github.com/uhoreg/ruma", rev = "9a53670f94095e9c27c1b1aff51b7cc3f0255c42", features = ["client-api-c", "compat-upload-signatures", "compat-user-id"] }
ruma-common = { git = "https://github.com/uhoreg/ruma", rev = "9a53670f94095e9c27c1b1aff51b7cc3f0255c42" }
ruma-client-api = { git = "https://github.com/uhoreg/ruma", rev = "9a53670f94095e9c27c1b1aff51b7cc3f0255c42" }
once_cell = "1.16.0"
rand = "0.8.5"
serde = "1.0.151"
Expand Down
109 changes: 108 additions & 1 deletion bindings/matrix-sdk-crypto-ffi/src/backup_recovery_key.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,18 @@ pub struct MegolmV1BackupKey {
pub backup_algorithm: String,
}

#[derive(uniffi::Record)]
pub struct EncryptedSessionData {
/// Unpadded base64-encoded public half of the ephemeral key.
pub ephemeral: String,
/// Ciphertext, encrypted using AES-CBC-256 with PKCS#7 padding, encoded in base64.
pub ciphertext: String,
/// First 8 bytes of MAC key, encoded in base64.
pub mac: String,
/// MAC of the key, encoded in base64
pub signatures: Option<HashMap<String, HashMap<String, String>>>,
}

impl BackupRecoveryKey {
const KEY_SIZE: usize = 32;
const SALT_SIZE: usize = 32;
Expand Down Expand Up @@ -185,6 +197,56 @@ impl BackupRecoveryKey {
) -> Result<String, PkDecryptionError> {
self.inner.decrypt_v1(&ephemeral_key, &mac, &ciphertext).map_err(|e| e.into())
}

/// Try to decrypt a message that was encrypted using the public part of the
/// backup key.
pub fn decrypt_session_data(
&self,
user_id: String,
session_data: EncryptedSessionData,
uhoreg marked this conversation as resolved.
Show resolved Hide resolved
) -> Result<BackedUpRoomKey, PkDecryptionError> {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The compiler complains here about:

error[E0277]: the trait bound `BackedUpRoomKey: LowerReturn<UniFfiTag>` is not satisfied
  --> bindings/matrix-sdk-crypto-ffi/src/backup_recovery_key.rs:88:1
   |
88 | #[uniffi::export]
   | ^^^^^^^^^^^^^^^^^ the trait `LowerReturn<UniFfiTag>` is not implemented for `BackedUpRoomKey`
   |
   = help: the following other types implement trait `LowerReturn<UT>`:
             <bool as LowerReturn<UT>>
             <i8 as LowerReturn<UT>>
             <i16 as LowerReturn<UT>>
             <i32 as LowerReturn<UT>>
             <i64 as LowerReturn<UT>>
             <u8 as LowerReturn<UT>>
             <u16 as LowerReturn<UT>>
             <u32 as LowerReturn<UT>>
           and 59 others
   = note: required for `Result<BackedUpRoomKey, PkDecryptionError>` to implement `LowerReturn<UniFfiTag>`
   = note: this error originates in the attribute macro `uniffi::export` (in Nightly builds, run with -Z macro-backtrace for more info)

error[E0277]: the trait bound `BackedUpRoomKey: LowerReturn<UniFfiTag>` is not satisfied
   --> bindings/matrix-sdk-crypto-ffi/src/backup_recovery_key.rs:210:10
    |
210 |     ) -> Result<BackedUpRoomKey, PkDecryptionError> {
    |          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `LowerReturn<UniFfiTag>` is not implemented for `BackedUpRoomKey`
    |
    = help: the following other types implement trait `LowerReturn<UT>`:
              <bool as LowerReturn<UT>>
              <i8 as LowerReturn<UT>>
              <i16 as LowerReturn<UT>>
              <i32 as LowerReturn<UT>>
              <i64 as LowerReturn<UT>>
              <u8 as LowerReturn<UT>>
              <u16 as LowerReturn<UT>>
              <u32 as LowerReturn<UT>>
            and 59 others
    = note: required for `Result<BackedUpRoomKey, PkDecryptionError>` to implement `LowerReturn<UniFfiTag>`

I'm not sure what's the best way to fix it. Does BackedUpRoomKey need to implement some trait? Or get wrapped in something?

let session_data = match &session_data.signatures {
Some(signatures) => {
let signatures_map = BTreeMap::from_iter(
signatures.iter().map(|(user, device_sigs)| {
(OwnedUserId::try_from(user).unwrap(), BTreeMap::from_iter(device_sigs.iter().map(|(key_id, signature)| (OwnedDeviceKeyId::try_from(key_id).unwrap(), signature))))
})
);
matrix_sdk_crypto::backup::keys::EncryptedSessionDataV2Init {
ephemeral: Base64::new(
vodozemac::base64_decode(session_data.ephemeral)
.map_err(|e| DecryptionError::Decoding(MessageDecodeError::Base64(e)).into())?
),
ciphertext: Base64::new(
vodozemac::base64_decode(session_data.ciphertext)
.map_err(|e| DecryptionError::Decoding(MessageDecodeError::Base64(e)).into())?
),
mac: Some(Base64::new(
vodozemac::base64_decode(session_data.mac)
.map_err(|e| DecryptionError::Decoding(MessageDecodeError::Base64(e)).into())?)
),
signatures: signatures_map,
}.into()
},
None => {
matrix_sdk_crypto::backup::keys::EncryptedSessionDataInit {
ephemeral: Base64::new(
vodozemac::base64_decode(session_data.ephemeral)
.map_err(|e| DecryptionError::Decoding(MessageDecodeError::Base64(e)).into())?
),
ciphertext: Base64::new(
vodozemac::base64_decode(session_data.ciphertext)
.map_err(|e| DecryptionError::Decoding(MessageDecodeError::Base64(e)).into())?
),
mac: Base64::new(
vodozemac::base64_decode(session_data.mac)
.map_err(|e| DecryptionError::Decoding(MessageDecodeError::Base64(e)).into())?
),
}.into()
},
};
self.inner.decrypt_session_data(user_id!(user_id), session_data).map_err(|e| e.into())
}
}

#[cfg(test)]
Expand All @@ -195,7 +257,7 @@ mod tests {
use super::BackupRecoveryKey;

#[test]
fn test_decrypt_key() {
fn test_decrypt_v1_key() {
let recovery_key = BackupRecoveryKey::from_base64(
"Ha9cklU/9NqFo9WKdVfGzmqUL/9wlkdxfEitbSIPVXw".to_owned(),
)
Expand Down Expand Up @@ -230,4 +292,49 @@ mod tests {
.decrypt_v1(ephemeral, mac, ciphertext)
.expect("The backed up key should be decrypted successfully");
}

#[test]
fn test_decrypt_key() {
let recovery_key = BackupRecoveryKey::from_base64(
"Ha9cklU/9NqFo9WKdVfGzmqUL/9wlkdxfEitbSIPVXw".to_owned(),
)
.unwrap();

let data = json!({
"first_message_index": 0,
"forwarded_count": 0,
"is_verified": false,
"session_data": {
"ephemeral": "HlLi76oV6wxHz3PCqE/bxJi6yF1HnYz5Dq3T+d/KpRw",
"ciphertext": "MuM8E3Yc6TSAvhVGb77rQ++jE6p9dRepx63/3YPD2wACKAppkZHeFrnTH6wJ/HSyrmzo\
7HfwqVl6tKNpfooSTHqUf6x1LHz+h4B/Id5ITO1WYt16AaI40LOnZqTkJZCfSPuE2oxa\
lwEHnCS3biWybutcnrBFPR3LMtaeHvvkb+k3ny9l5ZpsU9G7vCm3XoeYkWfLekWXvDhb\
qWrylXD0+CNUuaQJ/S527TzLd4XKctqVjjO/cCH7q+9utt9WJAfK8LGaWT/mZ3AeWjf5\
kiqOpKKf5Cn4n5SSil5p/pvGYmjnURvZSEeQIzHgvunIBEPtzK/MYEPOXe/P5achNGlC\
x+5N19Ftyp9TFaTFlTWCTi0mpD7ePfCNISrwpozAz9HZc0OhA8+1aSc7rhYFIeAYXFU3\
26NuFIFHI5pvpSxjzPQlOA+mavIKmiRAtjlLw11IVKTxgrdT4N8lXeMr4ndCSmvIkAzF\
Mo1uZA4fzjiAdQJE4/2WeXFNNpvdfoYmX8Zl9CAYjpSO5HvpwkAbk4/iLEH3hDfCVUwD\
fMh05PdGLnxeRpiEFWSMSsJNp+OWAA+5JsF41BoRGrxoXXT+VKqlUDONd+O296Psu8Q+\
d8/S618",
"mac": "GtMrurhDTwo",
"signatures": {
"@alice:localhost": {
"org.matrix.msc4048.hmac-sha-256:backup_mac_key": "Q3Z4X36MdXmBzo0TwnCKirEtWYGwJepfRRTft7cM6cU",
}
},
}
});

let key_backup_data: KeyBackupData = serde_json::from_value(data).unwrap();

let encrypted_session_data = EncryptedSessionData {
ephemeral: key_backup_data.session_data.ephemeral.encode(),
ciphertext: key_backup_data.session_data.ciphertext.encode(),
mac: key_backup_data.session_data.mac.encode(),
};

let _ = recovery_key
.decrypt_session_data(user_id!("@alice:localhost"), encrypted_session_data)
.expect("The backed up key should be decrypted successfully");
}
}
2 changes: 2 additions & 0 deletions crates/matrix-sdk-crypto/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ pbkdf2 = { version = "0.12.2", default-features = false }
rand = { workspace = true }
rmp-serde = "1.1.1"
ruma = { workspace = true, features = ["rand", "canonical-json", "unstable-msc3814"] }
ruma-common = { workspace = true, features = ["unstable-msc4048"] }
ruma-client-api = { workspace = true, features = ["unstable-msc4048"] }
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know this should be behind a feature flag, but how do I make a feature flag here enable the unstable-msc4048 feature flag in Ruma?

serde = { workspace = true, features = ["derive", "rc"] }
serde_json = { workspace = true }
sha2 = { workspace = true }
Expand Down
Loading