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 all 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
28 changes: 10 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 = "81aa5fc0ab5556ee9918d35298178c586c257100", features = ["client-api-c", "compat-upload-signatures", "compat-user-id", "compat-arbitrary-length-ids", "unstable-msc3401"] }
ruma-common = { git = "https://github.com/uhoreg/ruma", rev = "81aa5fc0ab5556ee9918d35298178c586c257100" }
ruma-client-api = { git = "https://github.com/uhoreg/ruma", rev = "81aa5fc0ab5556ee9918d35298178c586c257100" }
once_cell = "1.16.0"
rand = "0.8.5"
serde = "1.0.151"
Expand Down
97 changes: 94 additions & 3 deletions bindings/matrix-sdk-crypto-ffi/src/backup_recovery_key.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@ use std::{collections::HashMap, iter, ops::DerefMut, sync::Arc};

use hmac::Hmac;
use matrix_sdk_crypto::{
backups::DecryptionError,
backups::{DecryptionError, MessageDecodeError},
olm::BackedUpRoomKey,
store::{BackupDecryptionKey, CryptoStoreError as InnerStoreError},
};
use pbkdf2::pbkdf2;
use ruma::serde::Base64;
use rand::{distributions::Alphanumeric, thread_rng, Rng};
use sha2::Sha512;
use thiserror::Error;
Expand Down Expand Up @@ -63,6 +65,20 @@ 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,
/// v2 MAC of the backup data
// FIXME: should we be passing the entire `unsigned` field? Also, how do
// we handle other properties?
Comment on lines +77 to +78
Copy link
Member Author

Choose a reason for hiding this comment

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

should we be passing the entire unsigned field? Also, how do we handle other properties?

pub mac_v2: Option<String>,
}

impl BackupRecoveryKey {
const KEY_SIZE: usize = 32;
const SALT_SIZE: usize = 32;
Expand Down Expand Up @@ -185,17 +201,48 @@ 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,
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 ruma_session_data = ruma::api::client::backup::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();
if let Some(mac_v2) = session_data.mac_v2 {
ruma_session_data.unsigned = ruma::api::client::backup::EncryptedSessionDataUnsignedInit {
backup_mac: Base64::new(
vodozemac::base64_decode(mac_v2)
.map_err(|e| DecryptionError::Decoding(MessageDecodeError::Base64(e)))?
)
}
}
self.inner.decrypt_session_data(ruma_session_data).map_err(|e| e.into())
}
}

#[cfg(test)]
mod tests {
use ruma::api::client::backup::KeyBackupData;
use serde_json::json;

use super::BackupRecoveryKey;
use super::{BackupRecoveryKey, EncryptedSessionData};

#[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 +277,48 @@ 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",
"unsigned": {
"org.matrix.msc4048.backup_mac": "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(),
mac_v2: key_backup_data.session_data.unsigned.unwrap().backup_mac.unwrap().encode(),
};

let _ = recovery_key
.decrypt_session_data(encrypted_session_data)
.expect("The backed up key should be decrypted successfully");
}
}
1 change: 1 addition & 0 deletions crates/matrix-sdk-crypto/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ 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-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