1use std::{collections::BTreeMap, fmt};
6
7use ruma_common::{OwnedDeviceId, OwnedTransactionId, serde::Base64};
8use ruma_macros::EventContent;
9use serde::{Deserialize, Serialize};
10use serde_json::Value as JsonValue;
11
12use super::{
13 HashAlgorithm, KeyAgreementProtocol, MessageAuthenticationCode, ShortAuthenticationString,
14};
15use crate::relation::Reference;
16
17#[derive(Clone, Debug, Deserialize, Serialize, EventContent)]
21#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
22#[ruma_event(type = "m.key.verification.start", kind = ToDevice)]
23pub struct ToDeviceKeyVerificationStartEventContent {
24 pub from_device: OwnedDeviceId,
26
27 pub transaction_id: OwnedTransactionId,
33
34 #[serde(flatten)]
36 pub method: StartMethod,
37}
38
39impl ToDeviceKeyVerificationStartEventContent {
40 pub fn new(
43 from_device: OwnedDeviceId,
44 transaction_id: OwnedTransactionId,
45 method: StartMethod,
46 ) -> Self {
47 Self { from_device, transaction_id, method }
48 }
49}
50
51#[derive(Clone, Debug, Deserialize, Serialize, EventContent)]
55#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
56#[ruma_event(type = "m.key.verification.start", kind = MessageLike)]
57pub struct KeyVerificationStartEventContent {
58 pub from_device: OwnedDeviceId,
60
61 #[serde(flatten)]
63 pub method: StartMethod,
64
65 #[serde(rename = "m.relates_to")]
67 pub relates_to: Reference,
68}
69
70impl KeyVerificationStartEventContent {
71 pub fn new(from_device: OwnedDeviceId, method: StartMethod, relates_to: Reference) -> Self {
74 Self { from_device, method, relates_to }
75 }
76}
77
78#[derive(Clone, Debug, Deserialize, Serialize)]
80#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
81#[serde(untagged)]
82pub enum StartMethod {
83 SasV1(SasV1Content),
85
86 ReciprocateV1(ReciprocateV1Content),
92
93 #[doc(hidden)]
95 _Custom(_CustomContent),
96}
97
98#[doc(hidden)]
100#[derive(Clone, Debug, Deserialize, Serialize)]
101#[allow(clippy::exhaustive_structs)]
102pub struct _CustomContent {
103 pub method: String,
105
106 #[serde(flatten)]
108 pub data: BTreeMap<String, JsonValue>,
109}
110
111#[derive(Clone, Deserialize, Serialize)]
113#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
114#[serde(rename = "m.reciprocate.v1", tag = "method")]
115pub struct ReciprocateV1Content {
116 pub secret: Base64,
118}
119
120impl ReciprocateV1Content {
121 pub fn new(secret: Base64) -> Self {
125 Self { secret }
126 }
127}
128
129impl fmt::Debug for ReciprocateV1Content {
130 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
131 f.debug_struct("ReciprocateV1Content").finish_non_exhaustive()
132 }
133}
134
135#[derive(Clone, Debug, Deserialize, Serialize)]
140#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
141#[serde(rename = "m.sas.v1", tag = "method")]
142pub struct SasV1Content {
143 pub key_agreement_protocols: Vec<KeyAgreementProtocol>,
147
148 pub hashes: Vec<HashAlgorithm>,
152
153 pub message_authentication_codes: Vec<MessageAuthenticationCode>,
159
160 pub short_authentication_string: Vec<ShortAuthenticationString>,
164}
165
166#[derive(Debug)]
171#[allow(clippy::exhaustive_structs)]
172pub struct SasV1ContentInit {
173 pub key_agreement_protocols: Vec<KeyAgreementProtocol>,
177
178 pub hashes: Vec<HashAlgorithm>,
182
183 pub message_authentication_codes: Vec<MessageAuthenticationCode>,
189
190 pub short_authentication_string: Vec<ShortAuthenticationString>,
194}
195
196impl From<SasV1ContentInit> for SasV1Content {
197 fn from(init: SasV1ContentInit) -> Self {
199 Self {
200 key_agreement_protocols: init.key_agreement_protocols,
201 hashes: init.hashes,
202 message_authentication_codes: init.message_authentication_codes,
203 short_authentication_string: init.short_authentication_string,
204 }
205 }
206}
207
208#[cfg(test)]
209mod tests {
210 use std::collections::BTreeMap;
211
212 use assert_matches2::assert_matches;
213 use ruma_common::{canonical_json::assert_to_canonical_json_eq, event_id, serde::Base64};
214 use serde_json::{Value as JsonValue, from_value as from_json_value, json};
215
216 use super::{
217 _CustomContent, HashAlgorithm, KeyAgreementProtocol, KeyVerificationStartEventContent,
218 MessageAuthenticationCode, ReciprocateV1Content, SasV1ContentInit,
219 ShortAuthenticationString, StartMethod, ToDeviceKeyVerificationStartEventContent,
220 };
221 use crate::{ToDeviceEvent, relation::Reference};
222
223 #[test]
224 fn serialization() {
225 let key_verification_start_content = ToDeviceKeyVerificationStartEventContent {
226 from_device: "123".into(),
227 transaction_id: "456".into(),
228 method: StartMethod::SasV1(
229 SasV1ContentInit {
230 hashes: vec![HashAlgorithm::Sha256],
231 key_agreement_protocols: vec![KeyAgreementProtocol::Curve25519],
232 message_authentication_codes: vec![MessageAuthenticationCode::HkdfHmacSha256V2],
233 short_authentication_string: vec![ShortAuthenticationString::Decimal],
234 }
235 .into(),
236 ),
237 };
238
239 assert_to_canonical_json_eq!(
240 key_verification_start_content,
241 json!({
242 "from_device": "123",
243 "transaction_id": "456",
244 "method": "m.sas.v1",
245 "key_agreement_protocols": ["curve25519"],
246 "hashes": ["sha256"],
247 "message_authentication_codes": ["hkdf-hmac-sha256.v2"],
248 "short_authentication_string": ["decimal"],
249 }),
250 );
251
252 let key_verification_start_content = ToDeviceKeyVerificationStartEventContent {
253 from_device: "123".into(),
254 transaction_id: "456".into(),
255 method: StartMethod::_Custom(_CustomContent {
256 method: "m.sas.custom".to_owned(),
257 data: vec![("test".to_owned(), JsonValue::from("field"))]
258 .into_iter()
259 .collect::<BTreeMap<String, JsonValue>>(),
260 }),
261 };
262
263 assert_to_canonical_json_eq!(
264 key_verification_start_content,
265 json!({
266 "from_device": "123",
267 "transaction_id": "456",
268 "method": "m.sas.custom",
269 "test": "field",
270 }),
271 );
272
273 let secret = Base64::new(b"This is a secret to everybody".to_vec());
274
275 let key_verification_start_content = ToDeviceKeyVerificationStartEventContent {
276 from_device: "123".into(),
277 transaction_id: "456".into(),
278 method: StartMethod::ReciprocateV1(ReciprocateV1Content::new(secret.clone())),
279 };
280
281 assert_to_canonical_json_eq!(
282 key_verification_start_content,
283 json!({
284 "from_device": "123",
285 "method": "m.reciprocate.v1",
286 "secret": secret,
287 "transaction_id": "456",
288 }),
289 );
290 }
291
292 #[test]
293 fn in_room_serialization() {
294 let event_id = event_id!("$1598361704261elfgc:localhost");
295
296 let key_verification_start_content = KeyVerificationStartEventContent {
297 from_device: "123".into(),
298 relates_to: Reference { event_id: event_id.to_owned() },
299 method: StartMethod::SasV1(
300 SasV1ContentInit {
301 hashes: vec![HashAlgorithm::Sha256],
302 key_agreement_protocols: vec![KeyAgreementProtocol::Curve25519],
303 message_authentication_codes: vec![MessageAuthenticationCode::HkdfHmacSha256V2],
304 short_authentication_string: vec![ShortAuthenticationString::Decimal],
305 }
306 .into(),
307 ),
308 };
309
310 assert_to_canonical_json_eq!(
311 key_verification_start_content,
312 json!({
313 "from_device": "123",
314 "method": "m.sas.v1",
315 "key_agreement_protocols": ["curve25519"],
316 "hashes": ["sha256"],
317 "message_authentication_codes": ["hkdf-hmac-sha256.v2"],
318 "short_authentication_string": ["decimal"],
319 "m.relates_to": {
320 "rel_type": "m.reference",
321 "event_id": event_id,
322 },
323 }),
324 );
325
326 let secret = Base64::new(b"This is a secret to everybody".to_vec());
327
328 let key_verification_start_content = KeyVerificationStartEventContent {
329 from_device: "123".into(),
330 relates_to: Reference { event_id: event_id.to_owned() },
331 method: StartMethod::ReciprocateV1(ReciprocateV1Content::new(secret.clone())),
332 };
333
334 assert_to_canonical_json_eq!(
335 key_verification_start_content,
336 json!({
337 "from_device": "123",
338 "method": "m.reciprocate.v1",
339 "secret": secret,
340 "m.relates_to": {
341 "rel_type": "m.reference",
342 "event_id": event_id,
343 },
344 }),
345 );
346 }
347
348 #[test]
349 fn deserialization() {
350 let json = json!({
351 "from_device": "123",
352 "transaction_id": "456",
353 "method": "m.sas.v1",
354 "hashes": ["sha256"],
355 "key_agreement_protocols": ["curve25519"],
356 "message_authentication_codes": ["hkdf-hmac-sha256.v2"],
357 "short_authentication_string": ["decimal"]
358 });
359
360 let content = from_json_value::<ToDeviceKeyVerificationStartEventContent>(json).unwrap();
362 assert_eq!(content.from_device, "123");
363 assert_eq!(content.transaction_id, "456");
364
365 assert_matches!(content.method, StartMethod::SasV1(sas));
366 assert_eq!(sas.hashes, vec![HashAlgorithm::Sha256]);
367 assert_eq!(sas.key_agreement_protocols, vec![KeyAgreementProtocol::Curve25519]);
368 assert_eq!(
369 sas.message_authentication_codes,
370 vec![MessageAuthenticationCode::HkdfHmacSha256V2]
371 );
372 assert_eq!(sas.short_authentication_string, vec![ShortAuthenticationString::Decimal]);
373
374 let json = json!({
375 "content": {
376 "from_device": "123",
377 "transaction_id": "456",
378 "method": "m.sas.v1",
379 "key_agreement_protocols": ["curve25519"],
380 "hashes": ["sha256"],
381 "message_authentication_codes": ["hkdf-hmac-sha256.v2"],
382 "short_authentication_string": ["decimal"]
383 },
384 "type": "m.key.verification.start",
385 "sender": "@example:localhost",
386 });
387
388 let ev = from_json_value::<ToDeviceEvent<ToDeviceKeyVerificationStartEventContent>>(json)
389 .unwrap();
390 assert_eq!(ev.sender, "@example:localhost");
391 assert_eq!(ev.content.from_device, "123");
392 assert_eq!(ev.content.transaction_id, "456");
393
394 assert_matches!(ev.content.method, StartMethod::SasV1(sas));
395 assert_eq!(sas.hashes, vec![HashAlgorithm::Sha256]);
396 assert_eq!(sas.key_agreement_protocols, vec![KeyAgreementProtocol::Curve25519]);
397 assert_eq!(
398 sas.message_authentication_codes,
399 vec![MessageAuthenticationCode::HkdfHmacSha256V2]
400 );
401 assert_eq!(sas.short_authentication_string, vec![ShortAuthenticationString::Decimal]);
402
403 let json = json!({
404 "content": {
405 "from_device": "123",
406 "transaction_id": "456",
407 "method": "m.sas.custom",
408 "test": "field",
409 },
410 "type": "m.key.verification.start",
411 "sender": "@example:localhost",
412 });
413
414 let ev = from_json_value::<ToDeviceEvent<ToDeviceKeyVerificationStartEventContent>>(json)
415 .unwrap();
416 assert_eq!(ev.sender, "@example:localhost");
417 assert_eq!(ev.content.from_device, "123");
418 assert_eq!(ev.content.transaction_id, "456");
419
420 assert_matches!(ev.content.method, StartMethod::_Custom(custom));
421 assert_eq!(custom.method, "m.sas.custom");
422 assert_eq!(custom.data.get("test"), Some(&JsonValue::from("field")));
423
424 let json = json!({
425 "content": {
426 "from_device": "123",
427 "method": "m.reciprocate.v1",
428 "secret": "c2VjcmV0Cg",
429 "transaction_id": "456",
430 },
431 "type": "m.key.verification.start",
432 "sender": "@example:localhost",
433 });
434
435 let ev = from_json_value::<ToDeviceEvent<ToDeviceKeyVerificationStartEventContent>>(json)
436 .unwrap();
437 assert_eq!(ev.sender, "@example:localhost");
438 assert_eq!(ev.content.from_device, "123");
439 assert_eq!(ev.content.transaction_id, "456");
440
441 assert_matches!(ev.content.method, StartMethod::ReciprocateV1(reciprocate));
442 assert_eq!(reciprocate.secret.encode(), "c2VjcmV0Cg");
443 }
444
445 #[test]
446 fn in_room_deserialization() {
447 let json = json!({
448 "from_device": "123",
449 "method": "m.sas.v1",
450 "hashes": ["sha256"],
451 "key_agreement_protocols": ["curve25519"],
452 "message_authentication_codes": ["hkdf-hmac-sha256.v2"],
453 "short_authentication_string": ["decimal"],
454 "m.relates_to": {
455 "rel_type": "m.reference",
456 "event_id": "$1598361704261elfgc:localhost",
457 }
458 });
459
460 let content = from_json_value::<KeyVerificationStartEventContent>(json).unwrap();
462 assert_eq!(content.from_device, "123");
463 assert_eq!(content.relates_to.event_id, "$1598361704261elfgc:localhost");
464
465 assert_matches!(content.method, StartMethod::SasV1(sas));
466 assert_eq!(sas.hashes, vec![HashAlgorithm::Sha256]);
467 assert_eq!(sas.key_agreement_protocols, vec![KeyAgreementProtocol::Curve25519]);
468 assert_eq!(
469 sas.message_authentication_codes,
470 vec![MessageAuthenticationCode::HkdfHmacSha256V2]
471 );
472 assert_eq!(sas.short_authentication_string, vec![ShortAuthenticationString::Decimal]);
473
474 let json = json!({
475 "from_device": "123",
476 "method": "m.reciprocate.v1",
477 "secret": "c2VjcmV0Cg",
478 "m.relates_to": {
479 "rel_type": "m.reference",
480 "event_id": "$1598361704261elfgc:localhost",
481 }
482 });
483
484 let content = from_json_value::<KeyVerificationStartEventContent>(json).unwrap();
485 assert_eq!(content.from_device, "123");
486 assert_eq!(content.relates_to.event_id, "$1598361704261elfgc:localhost");
487
488 assert_matches!(content.method, StartMethod::ReciprocateV1(reciprocate));
489 assert_eq!(reciprocate.secret.encode(), "c2VjcmV0Cg");
490 }
491}