From bc03dc6f2ec381f854d6a66c3ce7da226dde730d Mon Sep 17 00:00:00 2001 From: Karlinde Date: Fri, 3 Jan 2020 00:49:29 +0100 Subject: [PATCH] Add endpoints for key management --- CHANGELOG.md | 1 + src/r0.rs | 1 + src/r0/keys.rs | 152 +++++++++++++++++++++++++++++++++ src/r0/keys/claim_keys.rs | 40 +++++++++ src/r0/keys/get_key_changes.rs | 36 ++++++++ src/r0/keys/get_keys.rs | 46 ++++++++++ src/r0/keys/upload_keys.rs | 35 ++++++++ 7 files changed, 311 insertions(+) create mode 100644 src/r0/keys.rs create mode 100644 src/r0/keys/claim_keys.rs create mode 100644 src/r0/keys/get_key_changes.rs create mode 100644 src/r0/keys/get_keys.rs create mode 100644 src/r0/keys/upload_keys.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index fd0b93e1..20dcf06c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ Improvements: * Add `r0::room::get_room_event` (introduced in r0.4.0) * Add `r0::read_marker::set_read_marker` (introduced in r0.4.0) * Add `r0::capabilities::get_capabilities` (introduced in r0.5.0) +* Add `r0::keys` endpoints (introduced in r0.3.0) # 0.5.0 diff --git a/src/r0.rs b/src/r0.rs index 93415eb7..79123f05 100644 --- a/src/r0.rs +++ b/src/r0.rs @@ -10,6 +10,7 @@ pub mod context; pub mod device; pub mod directory; pub mod filter; +pub mod keys; pub mod media; pub mod membership; pub mod message; diff --git a/src/r0/keys.rs b/src/r0/keys.rs new file mode 100644 index 00000000..e307d3b6 --- /dev/null +++ b/src/r0/keys.rs @@ -0,0 +1,152 @@ +//! Endpoints for key management + +use std::{ + collections::HashMap, + convert::TryFrom, + fmt::{Debug, Display, Error as FmtError, Formatter}, +}; + +use ruma_events::Algorithm; +use ruma_identifiers::{DeviceId, UserId}; +use serde::{ + de::{self, Unexpected}, + Deserialize, Deserializer, Serialize, Serializer, +}; + +pub mod claim_keys; +pub mod get_key_changes; +pub mod get_keys; +pub mod upload_keys; + +/// The basic key algorithms in the specification +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] +pub enum KeyAlgorithm { + /// The Ed25519 signature algorithm. + #[serde(rename = "ed25519")] + Ed25519, + + /// The Curve25519 ECDH algorithm. + #[serde(rename = "curve25519")] + Curve25519, + + /// The Curve25519 ECDH algorithm, but the key also contains signatures + #[serde(rename = "signed_curve25519")] + SignedCurve25519, +} + +impl Display for KeyAlgorithm { + fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), FmtError> { + let algorithm_str = match *self { + KeyAlgorithm::Ed25519 => "ed25519", + KeyAlgorithm::Curve25519 => "curve25519", + KeyAlgorithm::SignedCurve25519 => "signed_curve25519", + }; + write!(f, "{}", algorithm_str)?; + Ok(()) + } +} + +impl TryFrom<&'_ str> for KeyAlgorithm { + type Error = &'static str; + fn try_from(s: &str) -> Result { + match s { + "ed25519" => Ok(KeyAlgorithm::Ed25519), + "curve25519" => Ok(KeyAlgorithm::Curve25519), + "signed_curve25519" => Ok(KeyAlgorithm::SignedCurve25519), + _ => Err("Unknown algorithm"), + } + } +} + +/// A key algorithm and a device id, combined with a ':' +#[derive(Debug, Clone, Hash, PartialEq, Eq)] +pub struct AlgorithmAndDeviceId(pub KeyAlgorithm, pub DeviceId); + +impl Serialize for AlgorithmAndDeviceId { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let s = format!("{}:{}", self.0, self.1); + serializer.serialize_str(&s) + } +} + +impl<'de> Deserialize<'de> for AlgorithmAndDeviceId { + #[allow(clippy::comparison_chain)] + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let value = String::deserialize(deserializer)?; + let parts = value.split(':').collect::>(); + + const EXPECTED: &str = "a string composed of an algorithm and a device id separated by ':'"; + + if parts.len() < 2 { + return Err(de::Error::invalid_type( + Unexpected::Other("string without a ':' separator"), + &EXPECTED, + )); + } else if parts.len() > 2 { + return Err(de::Error::invalid_type( + Unexpected::Other("string with more than one ':' separator"), + &EXPECTED, + )); + } + + let algorithm_result = KeyAlgorithm::try_from(parts[0]); + match algorithm_result { + Ok(algorithm) => Ok(AlgorithmAndDeviceId(algorithm, parts[1].to_string())), + Err(_) => Err(de::Error::invalid_value( + Unexpected::Str(parts[0]), + &"valid key algorithm", + )), + } + } +} + +/// Identity keys for a device. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct DeviceKeys { + /// The ID of the user the device belongs to. Must match the user ID used when logging in. + pub user_id: UserId, + /// The ID of the device these keys belong to. Must match the device ID used when logging in. + pub device_id: DeviceId, + /// The encryption algorithms supported by this device. + pub algorithms: Vec, + /// Public identity keys. + pub keys: HashMap, + /// Signatures for the device key object. + pub signatures: HashMap>, + /// Additional data added to the device key information by intermediate servers, and + /// not covered by the signatures. + #[serde(skip_serializing_if = "Option::is_none")] + pub unsigned: Option, +} + +/// Additional data added to device key information by intermediate servers. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct UnsignedDeviceInfo { + /// The display name which the user set on the device. + device_display_name: String, +} + +/// A key for the SignedCurve25519 algorithm +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct SignedKey { + /// Base64-encoded 32-byte Curve25519 public key. + pub key: String, + /// Signatures for the key object. + pub signatures: HashMap>, +} + +/// A one-time public key for "pre-key" messages. +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(untagged)] +pub enum OneTimeKey { + /// A key containing signatures, for the SignedCurve25519 algorithm. + SignedKey(SignedKey), + /// A string-valued key, for the Ed25519 and Curve25519 algorithms. + Key(String), +} diff --git a/src/r0/keys/claim_keys.rs b/src/r0/keys/claim_keys.rs new file mode 100644 index 00000000..b21a62bd --- /dev/null +++ b/src/r0/keys/claim_keys.rs @@ -0,0 +1,40 @@ +//! [POST /_matrix/client/r0/keys/claim](https://matrix.org/docs/spec/client_server/r0.6.0#post-matrix-client-r0-keys-claim) + +use std::collections::HashMap; + +use js_int::UInt; +use ruma_api::ruma_api; +use ruma_identifiers::{DeviceId, UserId}; +use serde_json::Value; + +use super::{AlgorithmAndDeviceId, KeyAlgorithm, OneTimeKey}; + +ruma_api! { + metadata { + description: "Claims one-time keys for use in pre-key messages.", + method: POST, + name: "claim_keys", + path: "/_matrix/client/r0/keys/claim", + rate_limited: false, + requires_authentication: true, + } + + request { + /// The time (in milliseconds) to wait when downloading keys from remote servers. + /// 10 seconds is the recommended default. + #[serde(skip_serializing_if = "Option::is_none")] + pub timeout: Option, + + /// The keys to be claimed. + pub one_time_keys: HashMap>, + } + + response { + /// If any remote homeservers could not be reached, they are recorded here. + /// The names of the properties are the names of the unreachable servers. + pub failures: HashMap, + + /// One-time keys for the queried devices. + pub one_time_keys: HashMap>>, + } +} diff --git a/src/r0/keys/get_key_changes.rs b/src/r0/keys/get_key_changes.rs new file mode 100644 index 00000000..f8b8c22a --- /dev/null +++ b/src/r0/keys/get_key_changes.rs @@ -0,0 +1,36 @@ +//! [GET /_matrix/client/r0/keys/changes](https://matrix.org/docs/spec/client_server/r0.6.0#get-matrix-client-r0-keys-changes) + +use ruma_api::ruma_api; +use ruma_identifiers::UserId; + +ruma_api! { + metadata { + description: "Gets a list of users who have updated their device identity keys since a previous sync token.", + method: GET, + name: "get_key_changes", + path: "/_matrix/client/r0/keys/changes", + rate_limited: false, + requires_authentication: true, + } + + request { + /// The desired start point of the list. + /// Should be the next_batch field from a response to an earlier call to /sync. + #[ruma_api(query)] + pub from: String, + + /// The desired end point of the list. + /// Should be the next_batch field from a recent call to /sync - typically the most recent such call. + #[ruma_api(query)] + pub to: String, + } + + response { + /// The Matrix User IDs of all users who updated their device identity keys. + pub changed: Vec, + + /// The Matrix User IDs of all users who may have left all the end-to-end + /// encrypted rooms they previously shared with the user. + pub left: Vec + } +} diff --git a/src/r0/keys/get_keys.rs b/src/r0/keys/get_keys.rs new file mode 100644 index 00000000..e314ffba --- /dev/null +++ b/src/r0/keys/get_keys.rs @@ -0,0 +1,46 @@ +//! [POST /_matrix/client/r0/keys/query](https://matrix.org/docs/spec/client_server/r0.6.0#post-matrix-client-r0-keys-query) + +use std::collections::HashMap; + +use js_int::UInt; +use ruma_api::ruma_api; +use ruma_identifiers::{DeviceId, UserId}; +use serde_json::Value; + +use super::DeviceKeys; + +ruma_api! { + metadata { + description: "Returns the current devices and identity keys for the given users.", + method: POST, + name: "get_keys", + path: "/_matrix/client/r0/keys/query", + rate_limited: false, + requires_authentication: true, + } + + request { + /// The time (in milliseconds) to wait when downloading keys from remote servers. + /// 10 seconds is the recommended default. + #[serde(skip_serializing_if = "Option::is_none")] + pub timeout: Option, + + /// The keys to be downloaded. An empty list indicates all devices for the corresponding user. + pub device_keys: HashMap>, + + /// If the client is fetching keys as a result of a device update received in a sync request, + /// this should be the 'since' token of that sync request, or any later sync token. + /// This allows the server to ensure its response contains the keys advertised by the notification in that sync. + #[serde(skip_serializing_if = "Option::is_none")] + pub token: Option + } + + response { + /// If any remote homeservers could not be reached, they are recorded here. + /// The names of the properties are the names of the unreachable servers. + pub failures: HashMap, + + /// Information on the queried devices. + pub device_keys: HashMap>, + } +} diff --git a/src/r0/keys/upload_keys.rs b/src/r0/keys/upload_keys.rs new file mode 100644 index 00000000..64b692ac --- /dev/null +++ b/src/r0/keys/upload_keys.rs @@ -0,0 +1,35 @@ +//! [POST /_matrix/client/r0/keys/upload](https://matrix.org/docs/spec/client_server/r0.6.0#post-matrix-client-r0-keys-upload) + +use std::collections::HashMap; + +use js_int::UInt; +use ruma_api::ruma_api; + +use super::{AlgorithmAndDeviceId, DeviceKeys, KeyAlgorithm, OneTimeKey}; + +ruma_api! { + metadata { + description: "Publishes end-to-end encryption keys for the device.", + method: POST, + name: "upload_keys", + path: "/_matrix/client/r0/keys/upload", + rate_limited: false, + requires_authentication: true, + } + + request { + /// Identity keys for the device. May be absent if no new identity keys are required. + #[serde(skip_serializing_if = "Option::is_none")] + pub device_keys: Option, + + /// One-time public keys for "pre-key" messages. + #[serde(skip_serializing_if = "Option::is_none")] + pub one_time_keys: Option>, + } + + response { + /// For each key algorithm, the number of unclaimed one-time keys of that + /// type currently held on the server for this device. + pub one_time_key_counts: HashMap + } +}