-
Notifications
You must be signed in to change notification settings - Fork 268
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
Changes from all commits
e58bd7e
b234be3
2454306
c091a0b
c3be55c
1438753
6cd5665
0a66718
f697aa2
8eceee6
15ca4fa
3217845
dd191a5
04fdce3
a454bcd
7ed633e
0268eeb
22f4474
cdc025e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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; | ||
|
@@ -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? | ||
pub mac_v2: Option<String>, | ||
} | ||
|
||
impl BackupRecoveryKey { | ||
const KEY_SIZE: usize = 32; | ||
const SALT_SIZE: usize = 32; | ||
|
@@ -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> { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The compiler complains here about:
I'm not sure what's the best way to fix it. Does |
||
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(), | ||
) | ||
|
@@ -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"); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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"] } | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 |
||
serde = { workspace = true, features = ["derive", "rc"] } | ||
serde_json = { workspace = true } | ||
sha2 = { workspace = true } | ||
|
There was a problem hiding this comment.
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?