Add events from the end-to-end encryption module.

This commit is contained in:
Jimmy Cuadra 2019-06-14 22:14:11 -07:00
parent 12212789b3
commit a0a9799c81
16 changed files with 1124 additions and 1 deletions

24
src/dummy.rs Normal file
View File

@ -0,0 +1,24 @@
//! Types for the *m.dummy* event.
use std::collections::HashMap;
use serde::{Deserialize, Serialize};
event! {
/// This event type is used to indicate new Olm sessions for end-to-end encryption.
///
/// Typically it is encrypted as an *m.room.encrypted* event, then sent as a to-device event.
///
/// The event does not have any content associated with it. The sending client is expected to
/// send a key share request shortly after this message, causing the receiving client to process
/// this *m.dummy* event as the most recent event and using the keyshare request to set up the
/// session. The keyshare request and *m.dummy* combination should result in the original
/// sending client receiving keys over the newly established session.
pub struct DummyEvent(DummyEventContent) {}
}
/// The payload of an *m.dummy* event.
///
/// The values in the hash map are not meaningful. They are used to generate an empty JSON
/// object to support the structure used by the Matrix specification.
pub type DummyEventContent = HashMap<(), ()>;

47
src/forwarded_room_key.rs Normal file
View File

@ -0,0 +1,47 @@
//! Types for the *m.forwarded_room_key* event.
use ruma_identifiers::RoomId;
use serde::{Deserialize, Serialize};
use super::Algorithm;
event! {
/// This event type is used to forward keys for end-to-end encryption.
///
/// Typically it is encrypted as an *m.room.encrypted* event, then sent as a to-device event.
pub struct ForwardedRoomKeyEvent(ForwardedRoomKeyEventContent) {}
}
/// The payload of an *m.forwarded_room_key* event.
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
pub struct ForwardedRoomKeyEventContent {
/// The encryption algorithm the key in this event is to be used with.
pub algorithm: Algorithm,
/// The room where the key is used.
pub room_id: RoomId,
/// The Curve25519 key of the device which initiated the session originally.
pub sender_key: String,
/// The ID of the session that the key is for.
pub session_id: String,
/// The key to be exchanged.
pub session_key: String,
/// The Ed25519 key of the device which initiated the session originally.
///
/// It is "claimed" because the receiving device has no way to tell that the original room_key
/// actually came from a device which owns the private part of this key unless they have done
/// device verification.
pub sender_claimed_ed25519_key: String,
/// Chain of Curve25519 keys.
///
/// It starts out empty, but each time the key is forwarded to another device, the previous
/// sender in the chain is added to the end of the list. For example, if the key is forwarded
/// from A to B to C, this field is empty between A and B, and contains A's Curve25519 key
/// between B and C.
pub forwarding_curve25519_key_chain: Vec<String>,
}

3
src/key.rs Normal file
View File

@ -0,0 +1,3 @@
//! Modules for events in the *m.key* namespace.
pub mod verification;

117
src/key/verification.rs Normal file
View File

@ -0,0 +1,117 @@
//! Modules for events in the *m.key.verification* namespace.
//!
//! This module also contains types shared by events in its child namespaces.
use serde::{Deserialize, Serialize};
pub mod accept;
pub mod cancel;
pub mod key;
pub mod mac;
pub mod request;
pub mod start;
/// A hash algorithm.
#[derive(Clone, Copy, Debug, Serialize, PartialEq, Deserialize)]
pub enum HashAlgorithm {
/// The SHA256 hash algorithm.
#[serde(rename = "sha256")]
Sha256,
/// Additional variants may be added in the future and will not be considered breaking changes
/// to `ruma-events`.
#[doc(hidden)]
#[serde(skip)]
__Nonexhaustive,
}
impl_enum! {
HashAlgorithm {
Sha256 => "sha256",
}
}
/// A key agreement protocol.
#[derive(Clone, Copy, Debug, Serialize, PartialEq, Deserialize)]
pub enum KeyAgreementProtocol {
/// The [Curve25519](https://cr.yp.to/ecdh.html) key agreement protocol.
#[serde(rename = "curve25519")]
Curve25519,
/// Additional variants may be added in the future and will not be considered breaking changes
/// to `ruma-events`.
#[doc(hidden)]
#[serde(skip)]
__Nonexhaustive,
}
impl_enum! {
KeyAgreementProtocol {
Curve25519 => "curve25519",
}
}
/// A message authentication code algorithm.
#[derive(Clone, Copy, Debug, Serialize, PartialEq, Deserialize)]
pub enum MessageAuthenticationCode {
/// The HKDF-HMAC-SHA256 MAC.
#[serde(rename = "hkdf-hmac-sha256")]
HkdfHmacSha256,
/// Additional variants may be added in the future and will not be considered breaking changes
/// to `ruma-events`.
#[doc(hidden)]
#[serde(skip)]
__Nonexhaustive,
}
impl_enum! {
MessageAuthenticationCode {
HkdfHmacSha256 => "hkdf-hmac-sha256",
}
}
/// A Short Authentication String method.
#[derive(Clone, Copy, Debug, Serialize, PartialEq, Deserialize)]
pub enum ShortAuthenticationString {
/// The decimal method.
#[serde(rename = "decimal")]
Decimal,
/// The emoji method.
#[serde(rename = "emoji")]
Emoji,
/// Additional variants may be added in the future and will not be considered breaking changes
/// to `ruma-events`.
#[doc(hidden)]
#[serde(skip)]
__Nonexhaustive,
}
impl_enum! {
ShortAuthenticationString {
Decimal => "decimal",
Emoji => "emoji",
}
}
/// A Short Authentication String (SAS) verification method.
#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
pub enum VerificationMethod {
/// The *m.sas.v1* verification method.
#[serde(rename = "m.sas.v1")]
MSasV1,
/// Additional variants may be added in the future and will not be considered breaking changes
/// to `ruma-events`.
#[doc(hidden)]
#[serde(skip)]
__Nonexhaustive,
}
impl_enum! {
VerificationMethod {
MSasV1 => "m.sas.v1",
}
}

View File

@ -0,0 +1,51 @@
//! Types for the *m.key.verification.accept* event.
use serde::{Deserialize, Serialize};
use super::{
HashAlgorithm, KeyAgreementProtocol, MessageAuthenticationCode, ShortAuthenticationString,
VerificationMethod,
};
event! {
/// Accepts a previously sent *m.key.verification.start* messge.
///
/// Typically sent as a to-device event.
pub struct AcceptEvent(AcceptEventContent) {}
}
/// The payload of an *m.key.verification.accept* event.
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
pub struct AcceptEventContent {
/// An opaque identifier for the verification process.
///
/// Must be the same as the one used for the *m.key.verification.start* message.
pub transaction_id: String,
/// The verification method to use.
///
/// Must be `m.sas.v1`.
pub method: VerificationMethod,
/// The key agreement protocol the device is choosing to use, out of the options in the
/// *m.key.verification.start* message.
pub key_agreement_protocol: KeyAgreementProtocol,
/// The hash method the device is choosing to use, out of the options in the
/// *m.key.verification.start* message.
pub hash: HashAlgorithm,
/// The message authentication code the device is choosing to use, out of the options in the
/// *m.key.verification.start* message.
pub message_authentication_code: MessageAuthenticationCode,
/// The SAS methods both devices involved in the verification process understand.
///
/// Must be a subset of the options in the *m.key.verification.start* message.
pub short_authentication_string: Vec<ShortAuthenticationString>,
/// The hash (encoded as unpadded base64) of the concatenation of the device's ephemeral public
/// key (encoded as unpadded base64) and the canonical JSON representation of the
/// *m.key.verification.start* message.
pub commitment: String,
}

View File

@ -0,0 +1,189 @@
//! Types for the *m.key.verification.cancel* event.
use std::fmt::{Display, Formatter, Result as FmtResult};
use serde::{
de::{Error as SerdeError, Visitor},
Deserialize, Deserializer, Serialize, Serializer,
};
event! {
/// Cancels a key verification process/request.
///
/// Typically sent as a to-device event.
pub struct CancelEvent(CancelEventContent) {}
}
/// The payload of an *m.key.verification.cancel* event.
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
pub struct CancelEventContent {
/// The opaque identifier for the verification process/request.
pub transaction_id: String,
/// A human readable description of the `code`.
///
/// The client should only rely on this string if it does not understand the `code`.
pub reason: String,
/// The error code for why the process/request was cancelled by the user.
pub code: CancelCode,
}
/// An error code for why the process/request was cancelled by the user.
///
/// Custom error codes should use the Java package naming convention.
#[derive(Clone, Debug, PartialEq)]
pub enum CancelCode {
/// The user cancelled the verification.
User,
/// The verification process timed out. Verification processes can define their own timeout
/// parameters.
Timeout,
/// The device does not know about the given transaction ID.
UnknownTransaction,
/// The device does not know how to handle the requested method.
///
/// This should be sent for *m.key.verification.start* messages and messages defined by
/// individual verification processes.
UnknownMethod,
/// The device received an unexpected message.
///
/// Typically raised when one of the parties is handling the verification out of order.
UnexpectedMessage,
/// The key was not verified.
KeyMismatch,
/// The expected user did not match the user verified.
UserMismatch,
/// The message received was invalid.
InvalidMessage,
/// An *m.key.verification.request* was accepted by a different device.
///
/// The device receiving this error can ignore the verification request.
Accepted,
/// Any code that is not part of the specification.
Custom(String),
/// Additional variants may be added in the future and will not be considered breaking changes
/// to `ruma-events`.
#[doc(hidden)]
__Nonexhaustive,
}
impl Display for CancelCode {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
let cancel_code_str = match *self {
CancelCode::User => "m.user",
CancelCode::Timeout => "m.timeout",
CancelCode::UnknownTransaction => "m.unknown_transaction",
CancelCode::UnknownMethod => "m.unknown_method",
CancelCode::UnexpectedMessage => "m.unexpected_message",
CancelCode::KeyMismatch => "m.key_mismatch",
CancelCode::UserMismatch => "m.user_mismatch",
CancelCode::InvalidMessage => "m.invalid_message",
CancelCode::Accepted => "m.accepted",
CancelCode::Custom(ref cancel_code) => cancel_code,
CancelCode::__Nonexhaustive => {
panic!("__Nonexhaustive enum variant is not intended for use.")
}
};
write!(f, "{}", cancel_code_str)
}
}
impl<'a> From<&'a str> for CancelCode {
fn from(s: &'a str) -> CancelCode {
match s {
"m.user" => CancelCode::User,
"m.timeout" => CancelCode::Timeout,
"m.unknown_transaction" => CancelCode::UnknownTransaction,
"m.unknown_method" => CancelCode::UnknownMethod,
"m.unexpected_message" => CancelCode::UnexpectedMessage,
"m.key_mismatch" => CancelCode::KeyMismatch,
"m.user_mismatch" => CancelCode::UserMismatch,
"m.invalid_message" => CancelCode::InvalidMessage,
"m.accepted" => CancelCode::Accepted,
cancel_code => CancelCode::Custom(cancel_code.to_string()),
}
}
}
impl Serialize for CancelCode {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(&self.to_string())
}
}
impl<'de> Deserialize<'de> for CancelCode {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
struct CancelCodeVisitor;
impl<'de> Visitor<'de> for CancelCodeVisitor {
type Value = CancelCode;
fn expecting(&self, formatter: &mut Formatter<'_>) -> FmtResult {
write!(formatter, "an `m.key.verification.cancel` code as a string")
}
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
where
E: SerdeError,
{
Ok(CancelCode::from(v))
}
}
deserializer.deserialize_str(CancelCodeVisitor)
}
}
#[cfg(test)]
mod tests {
use serde_json::{from_str, to_string};
use super::CancelCode;
#[test]
fn cancel_codes_serialize_to_display_form() {
assert_eq!(to_string(&CancelCode::User).unwrap(), r#""m.user""#);
}
#[test]
fn custom_cancel_codes_serialize_to_display_form() {
assert_eq!(
to_string(&CancelCode::Custom("io.ruma.test".to_string())).unwrap(),
r#""io.ruma.test""#
);
}
#[test]
fn cancel_codes_deserialize_from_display_form() {
assert_eq!(
from_str::<CancelCode>(r#""m.user""#).unwrap(),
CancelCode::User
);
}
#[test]
fn custom_cancel_codes_deserialize_from_display_form() {
assert_eq!(
from_str::<CancelCode>(r#""io.ruma.test""#).unwrap(),
CancelCode::Custom("io.ruma.test".to_string())
)
}
}

View File

@ -0,0 +1,22 @@
//! Types for the *m.key.verification.key* event.
use serde::{Deserialize, Serialize};
event! {
/// Sends the ephemeral public key for a device to the partner device.
///
/// Typically sent as a to-device event.
pub struct KeyEvent(KeyEventContent) {}
}
/// The payload of an *m.key.verification.key* event.
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct KeyEventContent {
/// An opaque identifier for the verification process.
///
/// Must be the same as the one used for the *m.key.verification.start* message.
pub transaction_id: String,
/// The device's ephemeral public key, encoded as unpadded Base64.
pub key: String,
}

View File

@ -0,0 +1,29 @@
//! Types for the *m.key.verification.mac* event.
use ruma_signatures::SignatureSet;
use serde::{Deserialize, Serialize};
event! {
/// Sends the MAC of a device's key to the partner device.
///
/// Typically sent as a to-device event.
pub struct MacEvent(MacEventContent) {}
}
/// The payload for an *m.key.verification.mac* event.
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct MacEventContent {
/// An opaque identifier for the verification process.
///
/// Must be the same as the one used for the *m.key.verification.start* message.
pub transaction_id: String,
/// A map of the key ID to the MAC of the key, using the algorithm in the verification process.
///
/// The MAC is encoded as unpadded Base64.
pub mac: SignatureSet,
/// The MAC of the comma-separated, sorted, list of key IDs given in the `mac` property, encoded
/// as unpadded Base64.
pub keys: String,
}

View File

@ -0,0 +1,34 @@
//! Types for the *m.key.verification.request* event.
use ruma_identifiers::DeviceId;
use serde::{Deserialize, Serialize};
use super::VerificationMethod;
event! {
/// Requests a key verification with another user's devices.
///
/// Typically sent as a to-device event.
pub struct RequestEvent(RequestEventContent) {}
}
/// The payload of an *m.key.verification.request* event.
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
pub struct RequestEventContent {
/// The device ID which is initiating the request.
pub from_device: DeviceId,
/// An opaque identifier for the verification request.
///
/// Must be unique with respect to the devices involved.
pub transaction_id: String,
/// The verification methods supported by the sender.
pub methods: Vec<VerificationMethod>,
/// The POSIX timestamp in milliseconds for when the request was made.
///
/// If the request is in the future by more than 5 minutes or more than 10 minutes in the past,
/// the message should be ignored by the receiver.
pub timestamp: u64,
}

View File

@ -0,0 +1,173 @@
//! Types for the *m.key.verification.start* event.
use ruma_identifiers::DeviceId;
use serde::{de::Error, Deserialize, Deserializer, Serialize, Serializer};
use serde_json::{from_value, Value};
use super::{
HashAlgorithm, KeyAgreementProtocol, MessageAuthenticationCode, ShortAuthenticationString,
VerificationMethod,
};
event! {
/// Begins an SAS key verification process.
///
/// Typically sent as a to-device event.
pub struct StartEvent(StartEventContent) {}
}
/// The payload of an *m.key.verification.start* event.
#[derive(Clone, Debug, PartialEq)]
pub enum StartEventContent {
/// The *m.sas.v1* verification method.
MSasV1(MSasV1Content),
/// Additional variants may be added in the future and will not be considered breaking changes
/// to `ruma-events`.
#[doc(hidden)]
__Nonexhaustive,
}
/// The payload of an *m.key.verification.start* event using the *m.sas.v1* method.
#[derive(Clone, Debug, Serialize, PartialEq, Deserialize)]
pub struct MSasV1Content {
/// The device ID which is initiating the process.
pub from_device: DeviceId,
/// An opaque identifier for the verification process.
///
/// Must be unique with respect to the devices involved. Must be the same as the
/// `transaction_id` given in the *m.key.verification.request* if this process is originating
/// from a request.
pub transaction_id: String,
/// The verification method to use.
///
/// Must be `m.sas.v1`.
pub method: VerificationMethod,
/// Optional method to use to verify the other user's key with.
#[serde(skip_serializing_if = "Option::is_none")]
pub next_method: Option<VerificationMethod>,
/// The key agreement protocols the sending device understands.
///
/// Must include at least `curve25519`.
pub key_agreement_protocols: Vec<KeyAgreementProtocol>,
/// The hash methods the sending device understands.
///
/// Must include at least `sha256`.
pub hashes: Vec<HashAlgorithm>,
/// The message authentication codes that the sending device understands.
///
/// Must include at least `hkdf-hmac-sha256`.
pub message_authentication_codes: Vec<MessageAuthenticationCode>,
/// The SAS methods the sending device (and the sending device's user) understands.
///
/// Must include at least `decimal`. Optionally can include `emoji`.
pub short_authentication_string: Vec<ShortAuthenticationString>,
}
impl Serialize for StartEventContent {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
match *self {
StartEventContent::MSasV1(ref content) => content.serialize(serializer),
_ => panic!("Attempted to serialize __Nonexhaustive variant."),
}
}
}
impl<'de> Deserialize<'de> for StartEventContent {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let value: Value = Deserialize::deserialize(deserializer)?;
let method_value = match value.get("method") {
Some(value) => value.clone(),
None => return Err(D::Error::missing_field("method")),
};
let method = match from_value::<VerificationMethod>(method_value.clone()) {
Ok(method) => method,
Err(error) => return Err(D::Error::custom(error.to_string())),
};
match method {
VerificationMethod::MSasV1 => {
let content = match from_value::<MSasV1Content>(value) {
Ok(content) => content,
Err(error) => return Err(D::Error::custom(error.to_string())),
};
Ok(StartEventContent::MSasV1(content))
}
VerificationMethod::__Nonexhaustive => Err(D::Error::custom(
"Attempted to deserialize __Nonexhaustive variant.",
)),
}
}
}
#[cfg(test)]
mod tests {
use serde_json::{from_str, to_string};
use super::{
HashAlgorithm, KeyAgreementProtocol, MSasV1Content, MessageAuthenticationCode,
ShortAuthenticationString, StartEventContent, VerificationMethod,
};
#[test]
fn serializtion() {
let key_verification_start_content = StartEventContent::MSasV1(MSasV1Content {
from_device: "123".to_string(),
transaction_id: "456".to_string(),
method: VerificationMethod::MSasV1,
next_method: None,
hashes: vec![HashAlgorithm::Sha256],
key_agreement_protocols: vec![KeyAgreementProtocol::Curve25519],
message_authentication_codes: vec![MessageAuthenticationCode::HkdfHmacSha256],
short_authentication_string: vec![ShortAuthenticationString::Decimal],
});
assert_eq!(
to_string(&key_verification_start_content).unwrap(),
r#"{"from_device":"123","transaction_id":"456","method":"m.sas.v1","key_agreement_protocols":["curve25519"],"hashes":["sha256"],"message_authentication_codes":["hkdf-hmac-sha256"],"short_authentication_string":["decimal"]}"#
);
}
#[test]
fn deserialization() {
let key_verification_start_content = StartEventContent::MSasV1(MSasV1Content {
from_device: "123".to_string(),
transaction_id: "456".to_string(),
method: VerificationMethod::MSasV1,
next_method: None,
hashes: vec![HashAlgorithm::Sha256],
key_agreement_protocols: vec![KeyAgreementProtocol::Curve25519],
message_authentication_codes: vec![MessageAuthenticationCode::HkdfHmacSha256],
short_authentication_string: vec![ShortAuthenticationString::Decimal],
});
assert_eq!(
from_str::<StartEventContent>(
r#"{"from_device":"123","transaction_id":"456","method":"m.sas.v1","hashes":["sha256"],"key_agreement_protocols":["curve25519"],"message_authentication_codes":["hkdf-hmac-sha256"],"short_authentication_string":["decimal"]}"#
)
.unwrap(),
key_verification_start_content
);
}
#[test]
fn deserialization_failure() {
assert!(from_str::<StartEventContent>(r#"{"from_device":"123"}"#).is_err());
}
}

View File

@ -118,11 +118,16 @@ pub mod collections {
pub mod only;
}
pub mod direct;
pub mod dummy;
pub mod forwarded_room_key;
pub mod fully_read;
pub mod ignored_user_list;
pub mod key;
pub mod presence;
pub mod receipt;
pub mod room;
pub mod room_key;
pub mod room_key_request;
pub mod sticker;
pub mod stripped;
pub mod tag;
@ -411,6 +416,84 @@ impl<'de> Deserialize<'de> for EventType {
}
}
/// An encryption algorithm to be used to encrypt messages sent to a room.
#[derive(Clone, Debug, PartialEq)]
pub enum Algorithm {
/// Olm version 1 using Curve25519, AES-256, and SHA-256.
OlmV1Curve25519AesSha2,
/// Megolm version 1 using AES-256 and SHA-256.
MegolmV1AesSha2,
/// Any algorithm that is not part of the specification.
Custom(String),
/// Additional variants may be added in the future and will not be considered breaking changes
/// to `ruma-events`.
#[doc(hidden)]
__Nonexhaustive,
}
impl Display for Algorithm {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
let algorithm_str = match *self {
Algorithm::OlmV1Curve25519AesSha2 => "m.olm.v1.curve25519-aes-sha2",
Algorithm::MegolmV1AesSha2 => "m.megolm.v1.aes-sha2",
Algorithm::Custom(ref algorithm) => algorithm,
Algorithm::__Nonexhaustive => {
panic!("__Nonexhaustive enum variant is not intended for use.")
}
};
write!(f, "{}", algorithm_str)
}
}
impl<'a> From<&'a str> for Algorithm {
fn from(s: &'a str) -> Algorithm {
match s {
"m.olm.v1.curve25519-aes-sha2" => Algorithm::OlmV1Curve25519AesSha2,
"m.megolm.v1.aes-sha2" => Algorithm::MegolmV1AesSha2,
algorithm => Algorithm::Custom(algorithm.to_string()),
}
}
}
impl Serialize for Algorithm {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(&self.to_string())
}
}
impl<'de> Deserialize<'de> for Algorithm {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
struct CancelCodeVisitor;
impl<'de> Visitor<'de> for CancelCodeVisitor {
type Value = Algorithm;
fn expecting(&self, formatter: &mut Formatter<'_>) -> FmtResult {
write!(formatter, "an encryption algorithm code as a string")
}
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
where
E: SerdeError,
{
Ok(Algorithm::from(v))
}
}
deserializer.deserialize_str(CancelCodeVisitor)
}
}
/// Serde deserialization decorator to map empty Strings to None,
/// and forward non-empty Strings to the Deserialize implementation for T.
/// Useful for the typical
@ -445,7 +528,7 @@ fn default_true() -> bool {
mod tests {
use serde_json::{from_str, to_string};
use super::EventType;
use super::{Algorithm, EventType};
#[test]
fn event_types_serialize_to_display_form() {
@ -478,4 +561,36 @@ mod tests {
EventType::Custom("io.ruma.test".to_string())
)
}
#[test]
fn algorithms_serialize_to_display_form() {
assert_eq!(
to_string(&Algorithm::MegolmV1AesSha2).unwrap(),
r#""m.megolm.v1.aes-sha2""#
);
}
#[test]
fn custom_algorithms_serialize_to_display_form() {
assert_eq!(
to_string(&Algorithm::Custom("io.ruma.test".to_string())).unwrap(),
r#""io.ruma.test""#
);
}
#[test]
fn algorithms_deserialize_from_display_form() {
assert_eq!(
from_str::<Algorithm>(r#""m.megolm.v1.aes-sha2""#).unwrap(),
Algorithm::MegolmV1AesSha2
);
}
#[test]
fn custom_algorithms_deserialize_from_display_form() {
assert_eq!(
from_str::<Algorithm>(r#""io.ruma.test""#).unwrap(),
Algorithm::Custom("io.ruma.test".to_string())
)
}
}

View File

@ -10,6 +10,8 @@ pub mod aliases;
pub mod avatar;
pub mod canonical_alias;
pub mod create;
pub mod encrypted;
pub mod encryption;
pub mod guest_access;
pub mod history_visibility;
pub mod join_rules;

184
src/room/encrypted.rs Normal file
View File

@ -0,0 +1,184 @@
//! Types for the *m.room.encrypted* event.
use ruma_identifiers::DeviceId;
use serde::{de::Error, Deserialize, Deserializer, Serialize, Serializer};
use serde_json::{from_value, Value};
use crate::Algorithm;
room_event! {
/// This event type is used when sending encrypted events.
///
/// This type is to be used within a room. For a to-device event, use `EncryptedEventContent`
/// directly.
pub struct EncryptedEvent(EncryptedEventContent) {}
}
/// The payload of an *m.room.encrypted* event.
#[derive(Clone, Debug, PartialEq)]
pub enum EncryptedEventContent {
/// An event encrypted with *m.olm.v1.curve25519-aes-sha2*.
OlmV1Curve25519AesSha2(OlmV1Curve25519AesSha2Content),
/// An event encrypted with *m.megolm.v1.aes-sha2*.
MegolmV1AesSha2(MegolmV1AesSha2Content),
/// Additional variants may be added in the future and will not be considered breaking changes
/// to `ruma-events`.
#[doc(hidden)]
__Nonexhaustive,
}
/// The payload of an *m.room.encrypted* event using the *m.olm.v1.curve25519-aes-sha2* algorithm.
#[derive(Clone, Debug, Serialize, PartialEq, Deserialize)]
pub struct OlmV1Curve25519AesSha2Content {
/// The encryption algorithm used to encrypt this event.
pub algorithm: Algorithm,
/// The encrypted content of the event.
pub ciphertext: CiphertextInfo,
/// The Curve25519 key of the sender.
pub sender_key: String,
}
/// A map from the recipient Curve25519 identity key to ciphertext information.
///
/// Used for messages encrypted with the *m.olm.v1.curve25519-aes-sha2* algorithm.
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
pub struct CiphertextInfo {
/// The encrypted payload.
pub body: String,
/// The Olm message type.
#[serde(rename = "type")]
pub message_type: u64,
}
/// The payload of an *m.room.encrypted* event using the *m.megolm.v1.aes-sha2* algorithm.
#[derive(Clone, Debug, Serialize, PartialEq, Deserialize)]
pub struct MegolmV1AesSha2Content {
/// The encryption algorithm used to encrypt this event.
pub algorithm: Algorithm,
/// The encrypted content of the event.
pub ciphertext: String,
/// The Curve25519 key of the sender.
pub sender_key: String,
/// The ID of the sending device.
pub device_id: DeviceId,
/// The ID of the session used to encrypt the message.
pub session_id: String,
}
impl Serialize for EncryptedEventContent {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
match *self {
EncryptedEventContent::OlmV1Curve25519AesSha2(ref content) => {
content.serialize(serializer)
}
EncryptedEventContent::MegolmV1AesSha2(ref content) => content.serialize(serializer),
_ => panic!("Attempted to serialize __Nonexhaustive variant."),
}
}
}
impl<'de> Deserialize<'de> for EncryptedEventContent {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let value: Value = Deserialize::deserialize(deserializer)?;
let method_value = match value.get("algorithm") {
Some(value) => value.clone(),
None => return Err(D::Error::missing_field("algorithm")),
};
let method = match from_value::<Algorithm>(method_value.clone()) {
Ok(method) => method,
Err(error) => return Err(D::Error::custom(error.to_string())),
};
match method {
Algorithm::OlmV1Curve25519AesSha2 => {
let content = match from_value::<OlmV1Curve25519AesSha2Content>(value) {
Ok(content) => content,
Err(error) => return Err(D::Error::custom(error.to_string())),
};
Ok(EncryptedEventContent::OlmV1Curve25519AesSha2(content))
}
Algorithm::MegolmV1AesSha2 => {
let content = match from_value::<MegolmV1AesSha2Content>(value) {
Ok(content) => content,
Err(error) => return Err(D::Error::custom(error.to_string())),
};
Ok(EncryptedEventContent::MegolmV1AesSha2(content))
}
Algorithm::Custom(_) => Err(D::Error::custom(
"Custom algorithms are not supported by `EncryptedEventContent`.",
)),
Algorithm::__Nonexhaustive => Err(D::Error::custom(
"Attempted to deserialize __Nonexhaustive variant.",
)),
}
}
}
#[cfg(test)]
mod tests {
use serde_json::{from_str, to_string};
use super::{Algorithm, EncryptedEventContent, MegolmV1AesSha2Content};
#[test]
fn serializtion() {
let key_verification_start_content =
EncryptedEventContent::MegolmV1AesSha2(MegolmV1AesSha2Content {
algorithm: Algorithm::MegolmV1AesSha2,
ciphertext: "ciphertext".to_string(),
sender_key: "sender_key".to_string(),
device_id: "device_id".to_string(),
session_id: "session_id".to_string(),
});
assert_eq!(
to_string(&key_verification_start_content).unwrap(),
r#"{"algorithm":"m.megolm.v1.aes-sha2","ciphertext":"ciphertext","sender_key":"sender_key","device_id":"device_id","session_id":"session_id"}"#
);
}
#[test]
fn deserialization() {
let key_verification_start_content =
EncryptedEventContent::MegolmV1AesSha2(MegolmV1AesSha2Content {
algorithm: Algorithm::MegolmV1AesSha2,
ciphertext: "ciphertext".to_string(),
sender_key: "sender_key".to_string(),
device_id: "device_id".to_string(),
session_id: "session_id".to_string(),
});
assert_eq!(
from_str::<EncryptedEventContent>(
r#"{"algorithm":"m.megolm.v1.aes-sha2","ciphertext":"ciphertext","sender_key":"sender_key","device_id":"device_id","session_id":"session_id"}"#
)
.unwrap(),
key_verification_start_content
);
}
#[test]
fn deserialization_failure() {
assert!(
from_str::<EncryptedEventContent>(r#"{"algorithm":"m.megolm.v1.aes-sha2"}"#).is_err()
);
}
}

29
src/room/encryption.rs Normal file
View File

@ -0,0 +1,29 @@
//! Types for the *m.room.encryption* event.
use serde::{Deserialize, Serialize};
use crate::Algorithm;
state_event! {
/// Defines how messages sent in this room should be encrypted.
pub struct EncryptionEvent(EncryptionEventContent) {}
}
/// The payload of an *m.room.encryption* event.
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
pub struct EncryptionEventContent {
/// The encryption algorithm to be used to encrypt messages sent in this room.
///
/// Must be `m.megolm.v1.aes-sha2`.
pub algorithm: Algorithm,
/// How long the session should be used before changing it.
///
/// 604800000 (a week) is the recommended default.
pub rotation_period_ms: Option<u64>,
/// How many messages should be sent before changing the session.
///
/// 100 is the recommended default.
pub rotation_period_msgs: Option<u64>,
}

31
src/room_key.rs Normal file
View File

@ -0,0 +1,31 @@
//! Types for the *m.room_key* event.
use ruma_identifiers::RoomId;
use serde::{Deserialize, Serialize};
use super::Algorithm;
event! {
/// This event type is used to exchange keys for end-to-end encryption.
///
/// Typically it is encrypted as an *m.room.encrypted* event, then sent as a to-device event.
pub struct RoomKeyEvent(RoomKeyEventContent) {}
}
/// The payload of an *m.room_key* event.
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
pub struct RoomKeyEventContent {
/// The encryption algorithm the key in this event is to be used with.
///
/// Must be `m.megolm.v1.aes-sha2`.
pub algorithm: Algorithm,
/// The room where the key is used.
pub room_id: RoomId,
/// The ID of the session that the key is for.
pub session_id: String,
/// The key to be exchanged.
pub session_key: String,
}

73
src/room_key_request.rs Normal file
View File

@ -0,0 +1,73 @@
//! Types for the *m.room_key_request* event.
use ruma_identifiers::{DeviceId, RoomId};
use serde::{Deserialize, Serialize};
use super::Algorithm;
event! {
/// This event type is used to request keys for end-to-end encryption.
///
/// It is sent as an unencrypted to-device event.
pub struct RoomKeyRequestEvent(RoomKeyRequestEventContent) {}
}
/// The payload of an *m.room_key_request* event.
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
pub struct RoomKeyRequestEventContent {
/// Whether this is a new key request or a cancellation of a previous request.
pub action: Action,
/// Information about the requested key.
///
/// Required when action is `request`.
pub body: Option<RequestedKeyInfo>,
/// ID of the device requesting the key.
pub requesting_device_id: DeviceId,
/// A random string uniquely identifying the request for a key.
///
/// If the key is requested multiple times, it should be reused. It should also reused in order
/// to cancel a request.
pub request_id: String,
}
/// A new key request or a cancellation of a previous request.
#[derive(Clone, Copy, Deserialize, Debug, PartialEq, Serialize)]
pub enum Action {
/// Request a key.
Request,
/// Cancel a request for a key.
CancelRequest,
/// Additional variants may be added in the future and will not be considered breaking changes
/// to `ruma-events`.
#[doc(hidden)]
#[serde(skip)]
__Nonexhaustive,
}
impl_enum! {
Action {
Request => "request",
CancelRequest => "cancel_request",
}
}
/// Information about a requested key.
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
pub struct RequestedKeyInfo {
/// The encryption algorithm the requested key in this event is to be used with.
pub algorithm: Algorithm,
/// The room where the key is used.
pub room_id: RoomId,
/// The Curve25519 key of the device which initiated the session originally.
pub sender_key: String,
/// The ID of the session that the key is for.
pub session_id: String,
}