Add endpoints for key management

This commit is contained in:
Karlinde 2020-01-03 00:49:29 +01:00 committed by Jonas Platte
parent c0e8e3a84c
commit bc03dc6f2e
7 changed files with 311 additions and 0 deletions

View File

@ -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

View File

@ -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;

152
src/r0/keys.rs Normal file
View File

@ -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<Self, Self::Error> {
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<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
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<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let value = String::deserialize(deserializer)?;
let parts = value.split(':').collect::<Vec<_>>();
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<Algorithm>,
/// Public identity keys.
pub keys: HashMap<AlgorithmAndDeviceId, String>,
/// Signatures for the device key object.
pub signatures: HashMap<UserId, HashMap<AlgorithmAndDeviceId, String>>,
/// 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<UnsignedDeviceInfo>,
}
/// 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<UserId, HashMap<AlgorithmAndDeviceId, String>>,
}
/// 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),
}

40
src/r0/keys/claim_keys.rs Normal file
View File

@ -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<UInt>,
/// The keys to be claimed.
pub one_time_keys: HashMap<UserId, HashMap<DeviceId, KeyAlgorithm>>,
}
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<String, Value>,
/// One-time keys for the queried devices.
pub one_time_keys: HashMap<UserId, HashMap<DeviceId, HashMap<AlgorithmAndDeviceId, OneTimeKey>>>,
}
}

View File

@ -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<UserId>,
/// 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<UserId>
}
}

46
src/r0/keys/get_keys.rs Normal file
View File

@ -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<UInt>,
/// The keys to be downloaded. An empty list indicates all devices for the corresponding user.
pub device_keys: HashMap<UserId, Vec<DeviceId>>,
/// 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<String>
}
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<String, Value>,
/// Information on the queried devices.
pub device_keys: HashMap<UserId, HashMap<DeviceId, DeviceKeys>>,
}
}

View File

@ -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<DeviceKeys>,
/// One-time public keys for "pre-key" messages.
#[serde(skip_serializing_if = "Option::is_none")]
pub one_time_keys: Option<HashMap<AlgorithmAndDeviceId, OneTimeKey>>,
}
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<KeyAlgorithm, UInt>
}
}