1use std::collections::BTreeMap;
16
17use ruma::{
18 events::{
19 key::verification::{
20 cancel::CancelCode,
21 mac::{KeyVerificationMacEventContent, ToDeviceKeyVerificationMacEventContent},
22 },
23 relation::Reference,
24 AnyMessageLikeEventContent, AnyToDeviceEventContent,
25 },
26 serde::Base64,
27 DeviceKeyAlgorithm, DeviceKeyId, OwnedDeviceKeyId, UserId,
28};
29use sha2::{Digest, Sha256};
30use tracing::{trace, warn};
31use vodozemac::{sas::EstablishedSas, Curve25519PublicKey};
32
33use super::{sas_state::SupportedMacMethod, FlowId, OutgoingContent};
34use crate::{
35 identities::{DeviceData, UserIdentityData},
36 olm::StaticAccountData,
37 verification::event_enums::{MacContent, StartContent},
38 Emoji, OwnUserIdentityData,
39};
40
41#[derive(Clone, Debug)]
42pub struct SasIds {
43 pub account: StaticAccountData,
44 pub own_identity: Option<OwnUserIdentityData>,
45 pub other_device: DeviceData,
46 pub other_identity: Option<UserIdentityData>,
47}
48
49pub fn calculate_commitment(public_key: Curve25519PublicKey, content: &StartContent<'_>) -> Base64 {
60 let content = content.canonical_json();
61 let content_string = content.to_string();
62
63 Base64::new(
64 Sha256::new()
65 .chain_update(public_key.to_base64())
66 .chain_update(content_string)
67 .finalize()
68 .as_slice()
69 .to_owned(),
70 )
71}
72
73fn emoji_from_index(index: u8) -> Emoji {
84 match index {
92 0 => Emoji { symbol: "🐶", description: "Dog" },
93 1 => Emoji { symbol: "🐱", description: "Cat" },
94 2 => Emoji { symbol: "🦁", description: "Lion" },
95 3 => Emoji { symbol: "🐎", description: "Horse" },
96 4 => Emoji { symbol: "🦄", description: "Unicorn" },
97 5 => Emoji { symbol: "🐷", description: "Pig" },
98 6 => Emoji { symbol: "🐘", description: "Elephant" },
99 7 => Emoji { symbol: "🐰", description: "Rabbit" },
100 8 => Emoji { symbol: "🐼", description: "Panda" },
101 9 => Emoji { symbol: "🐓", description: "Rooster" },
102 10 => Emoji { symbol: "🐧", description: "Penguin" },
103 11 => Emoji { symbol: "🐢", description: "Turtle" },
104 12 => Emoji { symbol: "🐟", description: "Fish" },
105 13 => Emoji { symbol: "🐙", description: "Octopus" },
106 14 => Emoji { symbol: "🦋", description: "Butterfly" },
107 15 => Emoji { symbol: "🌷", description: "Flower" },
108 16 => Emoji { symbol: "🌳", description: "Tree" },
109 17 => Emoji { symbol: "🌵", description: "Cactus" },
110 18 => Emoji { symbol: "🍄", description: "Mushroom" },
111 19 => Emoji { symbol: "🌏", description: "Globe" },
112 20 => Emoji { symbol: "🌙", description: "Moon" },
113 21 => Emoji { symbol: "☁️", description: "Cloud" },
114 22 => Emoji { symbol: "🔥", description: "Fire" },
115 23 => Emoji { symbol: "🍌", description: "Banana" },
116 24 => Emoji { symbol: "🍎", description: "Apple" },
117 25 => Emoji { symbol: "🍓", description: "Strawberry" },
118 26 => Emoji { symbol: "🌽", description: "Corn" },
119 27 => Emoji { symbol: "🍕", description: "Pizza" },
120 28 => Emoji { symbol: "🎂", description: "Cake" },
121 29 => Emoji { symbol: "❤️", description: "Heart" },
122 30 => Emoji { symbol: "😀", description: "Smiley" },
123 31 => Emoji { symbol: "🤖", description: "Robot" },
124 32 => Emoji { symbol: "🎩", description: "Hat" },
125 33 => Emoji { symbol: "👓", description: "Glasses" },
126 34 => Emoji { symbol: "🔧", description: "Spanner" },
127 35 => Emoji { symbol: "🎅", description: "Santa" },
128 36 => Emoji { symbol: "👍", description: "Thumbs Up" },
129 37 => Emoji { symbol: "☂️", description: "Umbrella" },
130 38 => Emoji { symbol: "⌛", description: "Hourglass" },
131 39 => Emoji { symbol: "⏰", description: "Clock" },
132 40 => Emoji { symbol: "🎁", description: "Gift" },
133 41 => Emoji { symbol: "💡", description: "Light Bulb" },
134 42 => Emoji { symbol: "📕", description: "Book" },
135 43 => Emoji { symbol: "✏️", description: "Pencil" },
136 44 => Emoji { symbol: "📎", description: "Paperclip" },
137 45 => Emoji { symbol: "✂️", description: "Scissors" },
138 46 => Emoji { symbol: "🔒", description: "Lock" },
139 47 => Emoji { symbol: "🔑", description: "Key" },
140 48 => Emoji { symbol: "🔨", description: "Hammer" },
141 49 => Emoji { symbol: "☎️", description: "Telephone" },
142 50 => Emoji { symbol: "🏁", description: "Flag" },
143 51 => Emoji { symbol: "🚂", description: "Train" },
144 52 => Emoji { symbol: "🚲", description: "Bicycle" },
145 53 => Emoji { symbol: "✈️", description: "Aeroplane" },
146 54 => Emoji { symbol: "🚀", description: "Rocket" },
147 55 => Emoji { symbol: "🏆", description: "Trophy" },
148 56 => Emoji { symbol: "⚽", description: "Ball" },
149 57 => Emoji { symbol: "🎸", description: "Guitar" },
150 58 => Emoji { symbol: "🎺", description: "Trumpet" },
151 59 => Emoji { symbol: "🔔", description: "Bell" },
152 60 => Emoji { symbol: "⚓", description: "Anchor" },
153 61 => Emoji { symbol: "🎧", description: "Headphones" },
154 62 => Emoji { symbol: "📁", description: "Folder" },
155 63 => Emoji { symbol: "📌", description: "Pin" },
156 _ => panic!("Trying to fetch an emoji outside the allowed range"),
157 }
158}
159
160fn extra_mac_info_receive(ids: &SasIds, flow_id: &str) -> String {
169 format!(
170 "MATRIX_KEY_VERIFICATION_MAC{first_user}{first_device}\
171 {second_user}{second_device}{transaction_id}",
172 first_user = ids.other_device.user_id(),
173 first_device = ids.other_device.device_id(),
174 second_user = ids.account.user_id,
175 second_device = ids.account.device_id,
176 transaction_id = flow_id,
177 )
178}
179
180pub fn receive_mac_event(
196 sas: &EstablishedSas,
197 ids: &SasIds,
198 flow_id: &str,
199 sender: &UserId,
200 mac_method: SupportedMacMethod,
201 content: &MacContent<'_>,
202) -> Result<(Vec<DeviceData>, Vec<UserIdentityData>), CancelCode> {
203 let mut verified_devices = Vec::new();
204 let mut verified_identities = Vec::new();
205
206 let info = extra_mac_info_receive(ids, flow_id);
207
208 trace!(
209 ?sender,
210 device_id = ?ids.other_device.device_id(),
211 "Received a key.verification.mac event"
212 );
213
214 let mut keys = content.mac().keys().map(|k| k.as_str()).collect::<Vec<_>>();
215 keys.sort_unstable();
216 mac_method.verify_mac(sas, &keys.join(","), &format!("{info}KEY_IDS"), content.keys())?;
217
218 for (key_id, key_mac) in content.mac() {
219 trace!(
220 ?sender,
221 device_id = ?ids.other_device.device_id(),
222 key_id,
223 "Checking a SAS MAC",
224 );
225
226 let key_id: OwnedDeviceKeyId = match key_id.as_str().try_into() {
227 Ok(id) => id,
228 Err(_) => continue,
229 };
230
231 if let Some(key) = ids.other_device.keys().get(&key_id) {
232 mac_method.verify_mac(sas, &key.to_base64(), &format!("{info}{key_id}"), key_mac)?;
233 trace!(?sender, ?key_id, "Successfully verified a device key");
234 verified_devices.push(ids.other_device.clone());
235 } else if let Some(identity) = &ids.other_identity {
236 if let Some(key) = identity.master_key().get_key(&key_id) {
237 mac_method.verify_mac(
240 sas,
241 &key.to_base64(),
242 &format!("{info}{key_id}"),
243 key_mac,
244 )?;
245 trace!(?sender, ?key_id, "Successfully verified a master key");
246 verified_identities.push(identity.clone())
247 }
248 } else {
249 warn!(
250 "Key ID {key_id} in MAC event from {sender} {} doesn't belong to any device \
251 or user identity",
252 ids.other_device.device_id()
253 );
254 }
255 }
256
257 Ok((verified_devices, verified_identities))
258}
259
260fn extra_mac_info_send(ids: &SasIds, flow_id: &str) -> String {
269 format!(
270 "MATRIX_KEY_VERIFICATION_MAC{first_user}{first_device}\
271 {second_user}{second_device}{transaction_id}",
272 first_user = ids.account.user_id,
273 first_device = ids.account.device_id,
274 second_user = ids.other_device.user_id(),
275 second_device = ids.other_device.device_id(),
276 transaction_id = flow_id,
277 )
278}
279
280pub fn get_mac_content(
290 sas: &EstablishedSas,
291 ids: &SasIds,
292 flow_id: &FlowId,
293 mac_method: SupportedMacMethod,
294) -> OutgoingContent {
295 let mut mac: BTreeMap<String, Base64> = BTreeMap::new();
296
297 let key_id = DeviceKeyId::from_parts(DeviceKeyAlgorithm::Ed25519, &ids.account.device_id);
298 let key = ids.account.identity_keys.ed25519.to_base64();
299 let info = extra_mac_info_send(ids, flow_id.as_str());
300
301 mac.insert(key_id.to_string(), mac_method.calculate_mac(sas, &key, &format!("{info}{key_id}")));
302
303 if let Some(own_identity) = &ids.own_identity {
304 if own_identity.is_verified() {
305 if let Some(key) = own_identity.master_key().get_first_key() {
306 let key_id = format!("{}:{}", DeviceKeyAlgorithm::Ed25519, key.to_base64());
307
308 let calculated_mac =
309 mac_method.calculate_mac(sas, &key.to_base64(), &format!("{info}{key_id}"));
310
311 mac.insert(key_id, calculated_mac);
312 }
313 }
314 }
315
316 let mut keys: Vec<_> = mac.keys().map(|s| s.as_str()).collect();
317 keys.sort_unstable();
318
319 let keys = mac_method.calculate_mac(sas, &keys.join(","), &format!("{info}KEY_IDS"));
320
321 match flow_id {
322 FlowId::ToDevice(s) => AnyToDeviceEventContent::KeyVerificationMac(
323 ToDeviceKeyVerificationMacEventContent::new(s.clone(), mac, keys),
324 )
325 .into(),
326 FlowId::InRoom(r, e) => {
327 (
328 r.clone(),
329 AnyMessageLikeEventContent::KeyVerificationMac(
330 KeyVerificationMacEventContent::new(mac, keys, Reference::new(e.clone())),
331 ),
332 )
333 .into()
334 }
335 }
336}
337
338fn extra_info_sas(
349 ids: &SasIds,
350 own_pubkey: Curve25519PublicKey,
351 their_pubkey: Curve25519PublicKey,
352 flow_id: &str,
353 we_started: bool,
354) -> String {
355 let our_info =
356 format!("{}|{}|{}", ids.account.user_id, ids.account.device_id, own_pubkey.to_base64());
357 let their_info = format!(
358 "{}|{}|{}",
359 ids.other_device.user_id(),
360 ids.other_device.device_id(),
361 their_pubkey.to_base64()
362 );
363
364 let (first_info, second_info) =
365 if we_started { (our_info, their_info) } else { (their_info, our_info) };
366
367 let info = format!("MATRIX_KEY_VERIFICATION_SAS|{first_info}|{second_info}|{flow_id}");
368
369 trace!("Generated a SAS extra info: {}", info);
370
371 info
372}
373
374pub fn get_emoji(
394 sas: &EstablishedSas,
395 ids: &SasIds,
396 flow_id: &str,
397 we_started: bool,
398) -> [Emoji; 7] {
399 let bytes = sas.bytes(&extra_info_sas(
400 ids,
401 sas.our_public_key(),
402 sas.their_public_key(),
403 flow_id,
404 we_started,
405 ));
406
407 let indices = bytes.emoji_indices();
408
409 [
410 emoji_from_index(indices[0]),
411 emoji_from_index(indices[1]),
412 emoji_from_index(indices[2]),
413 emoji_from_index(indices[3]),
414 emoji_from_index(indices[4]),
415 emoji_from_index(indices[5]),
416 emoji_from_index(indices[6]),
417 ]
418}
419
420pub fn get_emoji_index(
441 sas: &EstablishedSas,
442 ids: &SasIds,
443 flow_id: &str,
444 we_started: bool,
445) -> [u8; 7] {
446 let bytes = sas.bytes(&extra_info_sas(
447 ids,
448 sas.our_public_key(),
449 sas.their_public_key(),
450 flow_id,
451 we_started,
452 ));
453
454 bytes.emoji_indices()
455}
456
457pub fn get_decimal(
477 sas: &EstablishedSas,
478 ids: &SasIds,
479 flow_id: &str,
480 we_started: bool,
481) -> (u16, u16, u16) {
482 let bytes = sas.bytes(&extra_info_sas(
483 ids,
484 sas.our_public_key(),
485 sas.their_public_key(),
486 flow_id,
487 we_started,
488 ));
489
490 bytes.decimals()
491}
492
493#[cfg(all(test, not(target_arch = "wasm32")))]
494mod tests {
495 use ruma::{
496 events::key::verification::start::ToDeviceKeyVerificationStartEventContent, serde::Base64,
497 };
498 use serde_json::json;
499 use vodozemac::Curve25519PublicKey;
500
501 use super::calculate_commitment;
502 use crate::verification::event_enums::StartContent;
503
504 #[test]
505 fn commitment_calculation() {
506 let commitment = Base64::parse("CCQmB4JCdB0FW21FdAnHj/Hu8+W9+Nb0vgwPEnZZQ4g").unwrap();
507
508 let public_key =
509 Curve25519PublicKey::from_base64("Q/NmNFEUS1fS+YeEmiZkjjblKTitrKOAk7cPEumcMlg")
510 .unwrap();
511 let content = json!({
512 "from_device":"XOWLHHFSWM",
513 "transaction_id":"bYxBsirjUJO9osar6ST4i2M2NjrYLA7l",
514 "method":"m.sas.v1",
515 "key_agreement_protocols":["curve25519-hkdf-sha256","curve25519"],
516 "hashes":["sha256"],
517 "message_authentication_codes":["hkdf-hmac-sha256","hmac-sha256"],
518 "short_authentication_string":["decimal","emoji"]
519 });
520
521 let content: ToDeviceKeyVerificationStartEventContent =
522 serde_json::from_value(content).unwrap();
523 let content = StartContent::from(&content);
524 let calculated_commitment = calculate_commitment(public_key, &content);
525
526 assert_eq!(commitment, calculated_commitment);
527 }
528}