use std::collections::BTreeMap;
use js_option::JsOption;
use ruma::{
serde::{JsonCastable, Raw},
DeviceKeyAlgorithm, DeviceKeyId, OwnedDeviceId, OwnedDeviceKeyId, OwnedUserId,
};
use serde::{Deserialize, Serialize};
use serde_json::{value::to_raw_value, Value};
use vodozemac::{Curve25519PublicKey, Ed25519PublicKey};
use super::{EventEncryptionAlgorithm, Signatures};
use crate::{
olm::{SignedJsonObject, VerifyJson},
SignatureError,
};
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
#[serde(try_from = "DeviceKeyHelper", into = "DeviceKeyHelper")]
pub struct DeviceKeys {
pub user_id: OwnedUserId,
pub device_id: OwnedDeviceId,
pub algorithms: Vec<EventEncryptionAlgorithm>,
pub keys: BTreeMap<OwnedDeviceKeyId, DeviceKey>,
pub signatures: Signatures,
#[serde(default, skip_serializing_if = "JsOption::is_undefined")]
pub dehydrated: JsOption<bool>,
#[serde(default, skip_serializing_if = "UnsignedDeviceInfo::is_empty")]
pub unsigned: UnsignedDeviceInfo,
#[serde(flatten)]
other: BTreeMap<String, Value>,
}
impl DeviceKeys {
pub fn new(
user_id: OwnedUserId,
device_id: OwnedDeviceId,
algorithms: Vec<EventEncryptionAlgorithm>,
keys: BTreeMap<OwnedDeviceKeyId, DeviceKey>,
signatures: Signatures,
) -> Self {
Self {
user_id,
device_id,
algorithms,
keys,
signatures,
dehydrated: JsOption::Undefined,
unsigned: Default::default(),
other: BTreeMap::new(),
}
}
pub fn to_raw<T>(&self) -> Raw<T> {
Raw::from_json(to_raw_value(&self).expect("Couldn't serialize device keys"))
}
pub fn get_key(&self, algorithm: DeviceKeyAlgorithm) -> Option<&DeviceKey> {
self.keys.get(&DeviceKeyId::from_parts(algorithm, &self.device_id))
}
pub fn curve25519_key(&self) -> Option<Curve25519PublicKey> {
self.get_key(DeviceKeyAlgorithm::Curve25519).and_then(|k| {
if let DeviceKey::Curve25519(k) = k {
Some(*k)
} else {
None
}
})
}
pub fn ed25519_key(&self) -> Option<Ed25519PublicKey> {
self.get_key(DeviceKeyAlgorithm::Ed25519).and_then(|k| {
if let DeviceKey::Ed25519(k) = k {
Some(*k)
} else {
None
}
})
}
pub fn has_signed(&self, signed_object: &impl SignedJsonObject) -> Result<(), SignatureError> {
let key = self.ed25519_key().ok_or(SignatureError::MissingSigningKey)?;
let key_id = &DeviceKeyId::from_parts(DeviceKeyAlgorithm::Ed25519, &self.device_id);
key.verify_json(&self.user_id, key_id, signed_object)
}
pub fn check_self_signature(&self) -> Result<(), SignatureError> {
self.has_signed(self)
}
}
impl JsonCastable<DeviceKeys> for ruma::encryption::DeviceKeys {}
#[derive(Clone, Debug, Default, Deserialize, Serialize, PartialEq)]
pub struct UnsignedDeviceInfo {
#[serde(skip_serializing_if = "Option::is_none")]
pub device_display_name: Option<String>,
#[serde(flatten)]
other: BTreeMap<String, Value>,
}
impl UnsignedDeviceInfo {
pub fn new() -> Self {
Default::default()
}
pub fn is_empty(&self) -> bool {
self.device_display_name.is_none()
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum DeviceKey {
Curve25519(Curve25519PublicKey),
Ed25519(Ed25519PublicKey),
Unknown(String),
}
impl DeviceKey {
pub fn to_base64(&self) -> String {
match self {
DeviceKey::Curve25519(k) => k.to_base64(),
DeviceKey::Ed25519(k) => k.to_base64(),
DeviceKey::Unknown(k) => k.to_owned(),
}
}
}
impl From<Curve25519PublicKey> for DeviceKey {
fn from(val: Curve25519PublicKey) -> Self {
DeviceKey::Curve25519(val)
}
}
impl From<Ed25519PublicKey> for DeviceKey {
fn from(val: Ed25519PublicKey) -> Self {
DeviceKey::Ed25519(val)
}
}
#[derive(Clone, Debug, Deserialize, Serialize)]
struct DeviceKeyHelper {
pub user_id: OwnedUserId,
pub device_id: OwnedDeviceId,
pub algorithms: Vec<EventEncryptionAlgorithm>,
pub keys: BTreeMap<OwnedDeviceKeyId, String>,
#[serde(default, skip_serializing_if = "JsOption::is_undefined")]
pub dehydrated: JsOption<bool>,
pub signatures: Signatures,
#[serde(default, skip_serializing_if = "UnsignedDeviceInfo::is_empty")]
pub unsigned: UnsignedDeviceInfo,
#[serde(flatten)]
other: BTreeMap<String, Value>,
}
impl TryFrom<DeviceKeyHelper> for DeviceKeys {
type Error = vodozemac::KeyError;
fn try_from(value: DeviceKeyHelper) -> Result<Self, Self::Error> {
let keys: Result<BTreeMap<OwnedDeviceKeyId, DeviceKey>, vodozemac::KeyError> = value
.keys
.into_iter()
.map(|(k, v)| {
let key = match k.algorithm() {
DeviceKeyAlgorithm::Ed25519 => {
DeviceKey::Ed25519(Ed25519PublicKey::from_base64(&v)?)
}
DeviceKeyAlgorithm::Curve25519 => {
DeviceKey::Curve25519(Curve25519PublicKey::from_base64(&v)?)
}
_ => DeviceKey::Unknown(v),
};
Ok((k, key))
})
.collect();
Ok(Self {
user_id: value.user_id,
device_id: value.device_id,
algorithms: value.algorithms,
keys: keys?,
dehydrated: value.dehydrated,
signatures: value.signatures,
unsigned: value.unsigned,
other: value.other,
})
}
}
impl From<DeviceKeys> for DeviceKeyHelper {
fn from(value: DeviceKeys) -> Self {
let keys: BTreeMap<OwnedDeviceKeyId, String> =
value.keys.into_iter().map(|(k, v)| (k, v.to_base64())).collect();
Self {
user_id: value.user_id,
device_id: value.device_id,
algorithms: value.algorithms,
keys,
dehydrated: value.dehydrated,
signatures: value.signatures,
unsigned: value.unsigned,
other: value.other,
}
}
}
#[cfg(test)]
mod tests {
use std::str::FromStr;
use ruma::{device_id, user_id, OwnedDeviceKeyId};
use serde_json::json;
use vodozemac::{Curve25519PublicKey, Curve25519SecretKey};
use super::DeviceKeys;
#[test]
fn serialization() {
let json = json!({
"algorithms": vec![
"m.olm.v1.curve25519-aes-sha2",
"m.megolm.v1.aes-sha2"
],
"device_id": "BNYQQWUMXO",
"user_id": "@example:localhost",
"keys": {
"curve25519:BNYQQWUMXO": "xfgbLIC5WAl1OIkpOzoxpCe8FsRDT6nch7NQsOb15nc",
"ed25519:BNYQQWUMXO": "2/5LWJMow5zhJqakV88SIc7q/1pa8fmkfgAzx72w9G4"
},
"signatures": {
"@example:localhost": {
"ed25519:BNYQQWUMXO": "kTwMrbsLJJM/uFGOj/oqlCaRuw7i9p/6eGrTlXjo8UJMCFAetoyWzoMcF35vSe4S6FTx8RJmqX6rM7ep53MHDQ"
}
},
"unsigned": {
"device_display_name": "Alice's mobile phone",
"other_data": "other_value"
},
"other_data": "other_value"
});
let device_keys: DeviceKeys =
serde_json::from_value(json.clone()).expect("Can't deserialize device keys");
assert_eq!(device_keys.user_id, user_id!("@example:localhost"));
assert_eq!(&device_keys.device_id, device_id!("BNYQQWUMXO"));
let serialized = serde_json::to_value(device_keys).expect("Can't reserialize device keys");
assert_eq!(json, serialized);
}
#[test]
fn test_check_self_signature() {
let mut device_keys: DeviceKeys = serde_json::from_value(json!({
"algorithms": vec![
"m.olm.v1.curve25519-aes-sha2",
"m.megolm.v1.aes-sha2"
],
"device_id": "BNYQQWUMXO",
"user_id": "@example:localhost",
"keys": {
"curve25519:BNYQQWUMXO": "xfgbLIC5WAl1OIkpOzoxpCe8FsRDT6nch7NQsOb15nc",
"ed25519:BNYQQWUMXO": "2/5LWJMow5zhJqakV88SIc7q/1pa8fmkfgAzx72w9G4"
},
"signatures": {
"@example:localhost": {
"ed25519:BNYQQWUMXO": "kTwMrbsLJJM/uFGOj/oqlCaRuw7i9p/6eGrTlXjo8UJMCFAetoyWzoMcF35vSe4S6FTx8RJmqX6rM7ep53MHDQ"
}
},
"unsigned": {
"device_display_name": "Alice's mobile phone"
}
})).expect("Can't deserialize device keys");
device_keys.check_self_signature().expect("Self-signature check failed");
let new_curve_key = Curve25519SecretKey::new();
let key_id = OwnedDeviceKeyId::from_str("curve25519:BNYQQWUMXO").unwrap();
device_keys.keys.insert(key_id, Curve25519PublicKey::from(&new_curve_key).into());
assert!(device_keys.check_self_signature().is_err());
}
}