pub mod add_backup_keys;
pub mod add_backup_keys_for_room;
pub mod add_backup_keys_for_session;
pub mod create_backup_version;
pub mod delete_backup_keys;
pub mod delete_backup_keys_for_room;
pub mod delete_backup_keys_for_session;
pub mod delete_backup_version;
pub mod get_backup_info;
pub mod get_backup_keys;
pub mod get_backup_keys_for_room;
pub mod get_backup_keys_for_session;
pub mod get_latest_backup_info;
pub mod update_backup_version;
use std::{borrow::Cow, collections::BTreeMap};
use js_int::UInt;
use ruma_common::{
CrossSigningOrDeviceSignatures,
serde::{Base64, JsonObject, Raw, from_raw_json_value},
};
use serde::{Deserialize, Deserializer, Serialize};
use serde_json::{Value as JsonValue, value::RawValue as RawJsonValue};
#[derive(Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
pub struct RoomKeyBackup {
pub sessions: BTreeMap<String, Raw<KeyBackupData>>,
}
impl RoomKeyBackup {
pub fn new(sessions: BTreeMap<String, Raw<KeyBackupData>>) -> Self {
Self { sessions }
}
}
#[derive(Clone, Debug, Serialize)]
#[serde(tag = "algorithm", content = "auth_data")]
#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
pub enum BackupAlgorithm {
#[serde(rename = "m.megolm_backup.v1.curve25519-aes-sha2")]
MegolmBackupV1Curve25519AesSha2(MegolmBackupV1Curve25519AesSha2AuthData),
#[doc(hidden)]
#[serde(untagged)]
_Custom(CustomBackupAlgorithm),
}
impl BackupAlgorithm {
pub fn algorithm(&self) -> &str {
match self {
Self::MegolmBackupV1Curve25519AesSha2(_) => "m.megolm_backup.v1.curve25519-aes-sha2",
Self::_Custom(c) => &c.algorithm,
}
}
pub fn auth_data(&self) -> Cow<'_, JsonObject> {
fn serialize<T: Serialize>(obj: &T) -> JsonObject {
match serde_json::to_value(obj).expect("backup data serialization to succeed") {
JsonValue::Object(obj) => obj,
_ => panic!("all backup data types must serialize to objects"),
}
}
match self {
Self::MegolmBackupV1Curve25519AesSha2(d) => Cow::Owned(serialize(d)),
Self::_Custom(c) => Cow::Borrowed(&c.auth_data),
}
}
}
impl<'de> Deserialize<'de> for BackupAlgorithm {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
#[derive(Deserialize)]
struct BackupAlgorithmDeHelper {
algorithm: String,
auth_data: Box<RawJsonValue>,
}
let BackupAlgorithmDeHelper { algorithm, auth_data } =
BackupAlgorithmDeHelper::deserialize(deserializer)?;
Ok(match algorithm.as_ref() {
"m.megolm_backup.v1.curve25519-aes-sha2" => {
Self::MegolmBackupV1Curve25519AesSha2(from_raw_json_value(&auth_data)?)
}
_ => Self::_Custom(CustomBackupAlgorithm {
algorithm,
auth_data: from_raw_json_value(&auth_data)?,
}),
})
}
}
impl From<MegolmBackupV1Curve25519AesSha2AuthData> for BackupAlgorithm {
fn from(value: MegolmBackupV1Curve25519AesSha2AuthData) -> Self {
Self::MegolmBackupV1Curve25519AesSha2(value)
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
pub struct MegolmBackupV1Curve25519AesSha2AuthData {
pub public_key: Base64,
pub signatures: CrossSigningOrDeviceSignatures,
}
impl MegolmBackupV1Curve25519AesSha2AuthData {
pub fn new(public_key: Base64) -> Self {
Self { public_key, signatures: Default::default() }
}
}
#[doc(hidden)]
#[derive(Clone, Debug, Serialize)]
pub struct CustomBackupAlgorithm {
algorithm: String,
auth_data: JsonObject,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
pub struct KeyBackupData {
pub first_message_index: UInt,
pub forwarded_count: UInt,
pub is_verified: bool,
pub session_data: EncryptedSessionData,
}
#[derive(Debug)]
#[allow(clippy::exhaustive_structs)]
pub struct KeyBackupDataInit {
pub first_message_index: UInt,
pub forwarded_count: UInt,
pub is_verified: bool,
pub session_data: EncryptedSessionData,
}
impl From<KeyBackupDataInit> for KeyBackupData {
fn from(init: KeyBackupDataInit) -> Self {
let KeyBackupDataInit { first_message_index, forwarded_count, is_verified, session_data } =
init;
Self { first_message_index, forwarded_count, is_verified, session_data }
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
pub struct EncryptedSessionData {
pub ephemeral: Base64,
pub ciphertext: Base64,
pub mac: Base64,
}
#[derive(Debug)]
#[allow(clippy::exhaustive_structs)]
pub struct EncryptedSessionDataInit {
pub ephemeral: Base64,
pub ciphertext: Base64,
pub mac: Base64,
}
impl From<EncryptedSessionDataInit> for EncryptedSessionData {
fn from(init: EncryptedSessionDataInit) -> Self {
let EncryptedSessionDataInit { ephemeral, ciphertext, mac } = init;
Self { ephemeral, ciphertext, mac }
}
}
#[cfg(test)]
mod tests {
use std::borrow::Cow;
use assert_matches2::{assert_let, assert_matches};
use ruma_common::{
SigningKeyAlgorithm, SigningKeyId, canonical_json::assert_to_canonical_json_eq,
owned_user_id, serde::Base64,
};
use serde_json::{Value as JsonValue, from_value as from_json_value, json};
use super::{BackupAlgorithm, MegolmBackupV1Curve25519AesSha2AuthData};
#[test]
fn megolm_v1_backup_algorithm_serialize_roundtrip() {
let json = json!({
"algorithm": "m.megolm_backup.v1.curve25519-aes-sha2",
"auth_data": {
"public_key": "YWJjZGVm",
"signatures": {
"@alice:example.org": {
"ed25519:DEVICEID": "signature",
},
},
},
});
let mut backup_algorithm =
MegolmBackupV1Curve25519AesSha2AuthData::new(Base64::new(b"abcdef".to_vec()));
backup_algorithm.signatures.insert_signature(
owned_user_id!("@alice:example.org"),
SigningKeyId::from_parts(SigningKeyAlgorithm::Ed25519, "DEVICEID".into()),
"signature".to_owned(),
);
assert_to_canonical_json_eq!(BackupAlgorithm::from(backup_algorithm), json.clone());
assert_let!(
Ok(BackupAlgorithm::MegolmBackupV1Curve25519AesSha2(auth_data)) = from_json_value(json)
);
assert_eq!(auth_data.public_key.as_bytes(), b"abcdef");
let user_signatures =
auth_data.signatures.get(&owned_user_id!("@alice:example.org")).unwrap();
let mut user_signatures_iter = user_signatures.iter();
let (key_id, signature) = user_signatures_iter.next().unwrap();
assert_eq!(key_id, "ed25519:DEVICEID");
assert_eq!(signature, "signature");
assert_matches!(user_signatures_iter.next(), None);
}
#[test]
fn custom_backup_algorithm_serialize_roundtrip() {
let json = json!({
"algorithm": "local.dev.unknown_algorithm",
"auth_data": {
"foo": "bar",
"signatures": {
"ed25519:DEVICEID": "signature",
},
},
});
let backup_algorithm = from_json_value::<BackupAlgorithm>(json.clone()).unwrap();
assert_eq!(backup_algorithm.algorithm(), "local.dev.unknown_algorithm");
assert_let!(Cow::Borrowed(auth_data) = backup_algorithm.auth_data());
assert_let!(Some(JsonValue::String(foo)) = auth_data.get("foo"));
assert_eq!(foo, "bar");
assert_let!(Some(JsonValue::Object(signatures)) = auth_data.get("signatures"));
assert_let!(Some(JsonValue::String(signature)) = signatures.get("ed25519:DEVICEID"));
assert_eq!(signature, "signature");
assert_to_canonical_json_eq!(backup_algorithm, json);
}
}