From 7ecd4a035f84c2893ff9b4bee1af1b75344fdfab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Fri, 31 Jul 2020 14:54:08 +0200 Subject: [PATCH] crypto: Split out the Sas logic into different files. --- matrix_sdk_crypto/src/verification/machine.rs | 2 +- matrix_sdk_crypto/src/verification/mod.rs | 420 +------------ .../src/verification/sas/helpers.rs | 417 +++++++++++++ matrix_sdk_crypto/src/verification/sas/mod.rs | 589 ++++++++++++++++++ .../verification/{sas.rs => sas/sas_state.rs} | 543 ++-------------- 5 files changed, 1062 insertions(+), 909 deletions(-) create mode 100644 matrix_sdk_crypto/src/verification/sas/helpers.rs create mode 100644 matrix_sdk_crypto/src/verification/sas/mod.rs rename matrix_sdk_crypto/src/verification/{sas.rs => sas/sas_state.rs} (60%) diff --git a/matrix_sdk_crypto/src/verification/machine.rs b/matrix_sdk_crypto/src/verification/machine.rs index 32ca399b..6d7d0263 100644 --- a/matrix_sdk_crypto/src/verification/machine.rs +++ b/matrix_sdk_crypto/src/verification/machine.rs @@ -23,7 +23,7 @@ use matrix_sdk_common::{ locks::RwLock, }; -use super::{content_to_request, Sas}; +use super::sas::{content_to_request, Sas}; use crate::{Account, CryptoStore, CryptoStoreError}; #[derive(Clone, Debug)] diff --git a/matrix_sdk_crypto/src/verification/mod.rs b/matrix_sdk_crypto/src/verification/mod.rs index 16371ce9..e42e5a50 100644 --- a/matrix_sdk_crypto/src/verification/mod.rs +++ b/matrix_sdk_crypto/src/verification/mod.rs @@ -12,25 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::collections::BTreeMap; -use std::convert::TryInto; - -use olm_rs::sas::OlmSas; - -use matrix_sdk_common::{ - api::r0::{ - keys::{AlgorithmAndDeviceId, KeyAlgorithm}, - to_device::{send_event_to_device::Request as ToDeviceRequest, DeviceIdOrAllDevices}, - }, - events::{ - key::verification::mac::MacEventContent, AnyToDeviceEventContent, EventType, ToDeviceEvent, - }, - identifiers::{DeviceId, UserId}, - uuid::Uuid, -}; - -use crate::{Account, Device}; - #[allow(dead_code)] mod machine; #[allow(dead_code)] @@ -39,407 +20,8 @@ mod sas; pub use machine::VerificationMachine; pub use sas::Sas; -#[derive(Clone, Debug)] -struct SasIds { - account: Account, - other_device: Device, -} - -/// Get a tuple of an emoji and a description of the emoji using a number. -/// -/// This is taken directly from the [spec] -/// -/// # Panics -/// -/// The spec defines 64 unique emojis, this function panics if the index is -/// bigger than 63. -/// -/// [spec]: https://matrix.org/docs/spec/client_server/latest#sas-method-emoji -fn emoji_from_index(index: u8) -> (&'static str, &'static str) { - match index { - 0 => ("🐶", "Dog"), - 1 => ("🐱", "Cat"), - 2 => ("🦁", "Lion"), - 3 => ("🐎", "Horse"), - 4 => ("🦄", "Unicorn"), - 5 => ("🐷", "Pig"), - 6 => ("🐘", "Elephant"), - 7 => ("🐰", "Rabbit"), - 8 => ("🐼", "Panda"), - 9 => ("🐓", "Rooster"), - 10 => ("🐧", "Penguin"), - 11 => ("🐢", "Turtle"), - 12 => ("🐟", "Fish"), - 13 => ("🐙", "Octopus"), - 14 => ("🦋", "Butterfly"), - 15 => ("🌷", "Flower"), - 16 => ("🌳", "Tree"), - 17 => ("🌵", "Cactus"), - 18 => ("🍄", "Mushroom"), - 19 => ("🌏", "Globe"), - 20 => ("🌙", "Moon"), - 21 => ("☁️", "Cloud"), - 22 => ("🔥", "Fire"), - 23 => ("🍌", "Banana"), - 24 => ("🍎", "Apple"), - 25 => ("🍓", "Strawberry"), - 26 => ("🌽", "Corn"), - 27 => ("🍕", "Pizza"), - 28 => ("🎂", "Cake"), - 29 => ("❤️", "Heart"), - 30 => ("😀", "Smiley"), - 31 => ("🤖", "Robot"), - 32 => ("🎩", "Hat"), - 33 => ("👓", "Glasses"), - 34 => ("🔧", "Spanner"), - 35 => ("🎅", "Santa"), - 36 => ("👍", "Thumbs up"), - 37 => ("☂️", "Umbrella"), - 38 => ("⌛", "Hourglass"), - 39 => ("⏰", "Clock"), - 40 => ("🎁", "Gift"), - 41 => ("💡", "Light Bulb"), - 42 => ("📕", "Book"), - 43 => ("✏️", "Pencil"), - 44 => ("📎", "Paperclip"), - 45 => ("✂️", "Scissors"), - 46 => ("🔒", "Lock"), - 47 => ("🔑", "Key"), - 48 => ("🔨", "Hammer"), - 49 => ("☎️", "Telephone"), - 50 => ("🏁", "Flag"), - 51 => ("🚂", "Train"), - 52 => ("🚲", "Bicycle"), - 53 => ("✈️", "Airplane"), - 54 => ("🚀", "Rocket"), - 55 => ("🏆", "Trophy"), - 56 => ("⚽", "Ball"), - 57 => ("🎸", "Guitar"), - 58 => ("🎺", "Trumpet"), - 59 => ("🔔", "Bell"), - 60 => ("⚓", "Anchor"), - 61 => ("🎧", "Headphones"), - 62 => ("📁", "Folder"), - 63 => ("📌", "Pin"), - _ => panic!("Trying to fetch an emoji outside the allowed range"), - } -} - -/// Get the extra info that will be used when we check the MAC of a -/// m.key.verification.key event. -/// -/// # Arguments -/// -/// * `ids` - The ids that are used for this SAS authentication flow. -/// -/// * `flow_id` - The unique id that identifies this SAS verification process. -fn extra_mac_info_receive(ids: &SasIds, flow_id: &str) -> String { - format!( - "MATRIX_KEY_VERIFICATION_MAC{first_user}{first_device}\ - {second_user}{second_device}{transaction_id}", - first_user = ids.other_device.user_id(), - first_device = ids.other_device.device_id(), - second_user = ids.account.user_id(), - second_device = ids.account.device_id(), - transaction_id = flow_id, - ) -} - -/// Get the content for a m.key.verification.mac event. -/// -/// Returns a tuple that contains the list of verified devices and the list of -/// verified master keys. -/// -/// # Arguments -/// -/// * `sas` - The Olm SAS object that can be used to MACs -/// -/// * `ids` - The ids that are used for this SAS authentication flow. -/// -/// * `flow_id` - The unique id that identifies this SAS verification process. -/// -/// * `event` - The m.key.verification.mac event that was sent to us by -/// the other side. -fn receive_mac_event( - sas: &OlmSas, - ids: &SasIds, - flow_id: &str, - event: &ToDeviceEvent, -) -> (Vec>, Vec) { - // TODO check the event and cancel if it isn't ok (sender, transaction id) - let mut verified_devices: Vec> = Vec::new(); - - let info = extra_mac_info_receive(&ids, flow_id); - - let mut keys = event.content.mac.keys().cloned().collect::>(); - keys.sort(); - let keys = sas - .calculate_mac(&keys.join(","), &format!("{}KEY_IDS", &info)) - .expect("Can't calculate SAS MAC"); - - if keys != event.content.keys { - panic!("Keys mac mismatch") - } - - for (key_id, key_mac) in &event.content.mac { - let split: Vec<&str> = key_id.splitn(2, ':').collect(); - - if split.len() != 2 { - continue; - } - - let algorithm: KeyAlgorithm = if let Ok(a) = split[0].try_into() { - a - } else { - continue; - }; - let id = split[1]; - - let device_key_id = AlgorithmAndDeviceId(algorithm, id.into()); - - if let Some(key) = ids.other_device.keys().get(&device_key_id) { - if key_mac - == &sas - .calculate_mac(key, &format!("{}{}", info, key_id)) - .expect("Can't calculate SAS MAC") - { - verified_devices.push(ids.other_device.device_id().into()); - } else { - // TODO cancel here - } - } - // TODO add an else branch for the master key here - } - - (verified_devices, vec![]) -} - -/// Get the extra info that will be used when we generate a MAC and need to send -/// it out -/// -/// # Arguments -/// -/// * `ids` - The ids that are used for this SAS authentication flow. -/// -/// * `flow_id` - The unique id that identifies this SAS verification process. -fn extra_mac_info_send(ids: &SasIds, flow_id: &str) -> String { - format!( - "MATRIX_KEY_VERIFICATION_MAC{first_user}{first_device}\ - {second_user}{second_device}{transaction_id}", - first_user = ids.account.user_id(), - first_device = ids.account.device_id(), - second_user = ids.other_device.user_id(), - second_device = ids.other_device.device_id(), - transaction_id = flow_id, - ) -} - -/// Get the content for a m.key.verification.mac event. -/// -/// # Arguments -/// -/// * `sas` - The Olm SAS object that can be used to generate the MAC -/// -/// * `ids` - The ids that are used for this SAS authentication flow. -/// -/// * `flow_id` - The unique id that identifies this SAS verification process. -/// -/// * `we_started` - Flag signaling if the SAS process was started on our side. -/// -/// # Panics -/// -/// This will panic if the public key of the other side wasn't set. -fn get_mac_content(sas: &OlmSas, ids: &SasIds, flow_id: &str) -> MacEventContent { - let mut mac: BTreeMap = BTreeMap::new(); - - let key_id = AlgorithmAndDeviceId(KeyAlgorithm::Ed25519, ids.account.device_id().into()); - let key = ids.account.identity_keys().ed25519(); - let info = extra_mac_info_send(ids, flow_id); - - mac.insert( - key_id.to_string(), - sas.calculate_mac(key, &format!("{}{}", info, key_id)) - .expect("Can't calculate SAS MAC"), - ); - - // TODO Add the cross signing master key here if we trust/have it. - - let mut keys = mac.keys().cloned().collect::>(); - keys.sort(); - let keys = sas - .calculate_mac(&keys.join(","), &format!("{}KEY_IDS", &info)) - .expect("Can't calculate SAS MAC"); - - MacEventContent { - transaction_id: flow_id.to_owned(), - keys, - mac, - } -} - -/// Get the extra info that will be used when we generate bytes for the short -/// auth string. -/// -/// # Arguments -/// -/// * `ids` - The ids that are used for this SAS authentication flow. -/// -/// * `flow_id` - The unique id that identifies this SAS verification process. -/// -/// * `we_started` - Flag signaling if the SAS process was started on our side. -fn extra_info_sas(ids: &SasIds, flow_id: &str, we_started: bool) -> String { - let (first_user, first_device, second_user, second_device) = if we_started { - ( - ids.account.user_id(), - ids.account.device_id(), - ids.other_device.user_id(), - ids.other_device.device_id(), - ) - } else { - ( - ids.other_device.user_id(), - ids.other_device.device_id(), - ids.account.user_id(), - ids.account.device_id(), - ) - }; - - format!( - "MATRIX_KEY_VERIFICATION_SAS{first_user}{first_device}\ - {second_user}{second_device}{transaction_id}", - first_user = first_user, - first_device = first_device, - second_user = second_user, - second_device = second_device, - transaction_id = flow_id, - ) -} - -/// Get the emoji version of the short authentication string. -/// -/// Returns a vector of tuples where the first element is the emoji and the -/// second element the English description of the emoji. -/// -/// # Arguments -/// -/// * `sas` - The Olm SAS object that can be used to generate bytes using the -/// shared secret. -/// -/// * `ids` - The ids that are used for this SAS authentication flow. -/// -/// * `flow_id` - The unique id that identifies this SAS verification process. -/// -/// * `we_started` - Flag signaling if the SAS process was started on our side. -/// -/// # Panics -/// -/// This will panic if the public key of the other side wasn't set. -fn get_emoji( - sas: &OlmSas, - ids: &SasIds, - flow_id: &str, - we_started: bool, -) -> Vec<(&'static str, &'static str)> { - let bytes: Vec = sas - .generate_bytes(&extra_info_sas(&ids, &flow_id, we_started), 6) - .expect("Can't generate bytes") - .into_iter() - .map(|b| b as u64) - .collect(); - - // Join the 6 bytes into one 64 bit unsigned int. This u64 will contain 48 - // bits from our 6 bytes. - let mut num: u64 = bytes[0] << 40; - num += bytes[1] << 32; - num += bytes[2] << 24; - num += bytes[3] << 16; - num += bytes[4] << 8; - num += bytes[5]; - - // Take the top 42 bits of our 48 bits from the u64 and convert each 6 bits - // into a 6 bit number. - let numbers = vec![ - ((num >> 42) & 63) as u8, - ((num >> 36) & 63) as u8, - ((num >> 30) & 63) as u8, - ((num >> 24) & 63) as u8, - ((num >> 18) & 63) as u8, - ((num >> 12) & 63) as u8, - ((num >> 6) & 63) as u8, - ]; - - // Convert the 6 bit number into a emoji/description tuple. - numbers.into_iter().map(emoji_from_index).collect() -} - -/// Get the decimal version of the short authentication string. -/// -/// Returns a tuple containing three 4 digit integer numbers that represent -/// the short auth string. -/// -/// # Arguments -/// -/// * `sas` - The Olm SAS object that can be used to generate bytes using the -/// shared secret. -/// -/// * `ids` - The ids that are used for this SAS authentication flow. -/// -/// * `flow_id` - The unique id that identifies this SAS verification process. -/// -/// * `we_started` - Flag signaling if the SAS process was started on our side. -/// -/// # Panics -/// -/// This will panic if the public key of the other side wasn't set. -fn get_decimal(sas: &OlmSas, ids: &SasIds, flow_id: &str, we_started: bool) -> (u32, u32, u32) { - let bytes: Vec = sas - .generate_bytes(&extra_info_sas(&ids, &flow_id, we_started), 5) - .expect("Can't generate bytes") - .into_iter() - .map(|b| b as u32) - .collect(); - - // This bitwise operation is taken from the [spec] - // [spec]: https://matrix.org/docs/spec/client_server/latest#sas-method-decimal - let first = bytes[0] << 5 | bytes[1] >> 3; - let second = (bytes[1] & 0x7) << 10 | bytes[2] << 2 | bytes[3] >> 6; - let third = (bytes[3] & 0x3F) << 7 | bytes[4] >> 1; - - (first + 1000, second + 1000, third + 1000) -} - -pub(crate) fn content_to_request( - recipient: &UserId, - recipient_device: &DeviceId, - content: AnyToDeviceEventContent, -) -> ToDeviceRequest { - let mut messages = BTreeMap::new(); - let mut user_messages = BTreeMap::new(); - - user_messages.insert( - DeviceIdOrAllDevices::DeviceId(recipient_device.into()), - serde_json::value::to_raw_value(&content).expect("Can't serialize to-device content"), - ); - messages.insert(recipient.clone(), user_messages); - - let event_type = match content { - AnyToDeviceEventContent::KeyVerificationAccept(_) => EventType::KeyVerificationAccept, - AnyToDeviceEventContent::KeyVerificationStart(_) => EventType::KeyVerificationStart, - AnyToDeviceEventContent::KeyVerificationKey(_) => EventType::KeyVerificationKey, - AnyToDeviceEventContent::KeyVerificationMac(_) => EventType::KeyVerificationMac, - AnyToDeviceEventContent::KeyVerificationCancel(_) => EventType::KeyVerificationCancel, - _ => unreachable!(), - }; - - ToDeviceRequest { - txn_id: Uuid::new_v4().to_string(), - event_type, - messages, - } -} - #[cfg(test)] -mod test { +pub(crate) mod test { use serde_json::Value; use matrix_sdk_common::{ diff --git a/matrix_sdk_crypto/src/verification/sas/helpers.rs b/matrix_sdk_crypto/src/verification/sas/helpers.rs new file mode 100644 index 00000000..066d51f3 --- /dev/null +++ b/matrix_sdk_crypto/src/verification/sas/helpers.rs @@ -0,0 +1,417 @@ +use std::collections::BTreeMap; +use std::convert::TryInto; + +use olm_rs::sas::OlmSas; + +use matrix_sdk_common::{ + api::r0::{ + keys::{AlgorithmAndDeviceId, KeyAlgorithm}, + to_device::{send_event_to_device::Request as ToDeviceRequest, DeviceIdOrAllDevices}, + }, + events::{ + key::verification::mac::MacEventContent, AnyToDeviceEventContent, EventType, ToDeviceEvent, + }, + identifiers::{DeviceId, UserId}, + uuid::Uuid, +}; + +use crate::{Account, Device}; + +#[derive(Clone, Debug)] +pub struct SasIds { + pub account: Account, + pub other_device: Device, +} + +/// Get a tuple of an emoji and a description of the emoji using a number. +/// +/// This is taken directly from the [spec] +/// +/// # Panics +/// +/// The spec defines 64 unique emojis, this function panics if the index is +/// bigger than 63. +/// +/// [spec]: https://matrix.org/docs/spec/client_server/latest#sas-method-emoji +fn emoji_from_index(index: u8) -> (&'static str, &'static str) { + match index { + 0 => ("🐶", "Dog"), + 1 => ("🐱", "Cat"), + 2 => ("🦁", "Lion"), + 3 => ("🐎", "Horse"), + 4 => ("🦄", "Unicorn"), + 5 => ("🐷", "Pig"), + 6 => ("🐘", "Elephant"), + 7 => ("🐰", "Rabbit"), + 8 => ("🐼", "Panda"), + 9 => ("🐓", "Rooster"), + 10 => ("🐧", "Penguin"), + 11 => ("🐢", "Turtle"), + 12 => ("🐟", "Fish"), + 13 => ("🐙", "Octopus"), + 14 => ("🦋", "Butterfly"), + 15 => ("🌷", "Flower"), + 16 => ("🌳", "Tree"), + 17 => ("🌵", "Cactus"), + 18 => ("🍄", "Mushroom"), + 19 => ("🌏", "Globe"), + 20 => ("🌙", "Moon"), + 21 => ("☁️", "Cloud"), + 22 => ("🔥", "Fire"), + 23 => ("🍌", "Banana"), + 24 => ("🍎", "Apple"), + 25 => ("🍓", "Strawberry"), + 26 => ("🌽", "Corn"), + 27 => ("🍕", "Pizza"), + 28 => ("🎂", "Cake"), + 29 => ("❤️", "Heart"), + 30 => ("😀", "Smiley"), + 31 => ("🤖", "Robot"), + 32 => ("🎩", "Hat"), + 33 => ("👓", "Glasses"), + 34 => ("🔧", "Spanner"), + 35 => ("🎅", "Santa"), + 36 => ("👍", "Thumbs up"), + 37 => ("☂️", "Umbrella"), + 38 => ("⌛", "Hourglass"), + 39 => ("⏰", "Clock"), + 40 => ("🎁", "Gift"), + 41 => ("💡", "Light Bulb"), + 42 => ("📕", "Book"), + 43 => ("✏️", "Pencil"), + 44 => ("📎", "Paperclip"), + 45 => ("✂️", "Scissors"), + 46 => ("🔒", "Lock"), + 47 => ("🔑", "Key"), + 48 => ("🔨", "Hammer"), + 49 => ("☎️", "Telephone"), + 50 => ("🏁", "Flag"), + 51 => ("🚂", "Train"), + 52 => ("🚲", "Bicycle"), + 53 => ("✈️", "Airplane"), + 54 => ("🚀", "Rocket"), + 55 => ("🏆", "Trophy"), + 56 => ("⚽", "Ball"), + 57 => ("🎸", "Guitar"), + 58 => ("🎺", "Trumpet"), + 59 => ("🔔", "Bell"), + 60 => ("⚓", "Anchor"), + 61 => ("🎧", "Headphones"), + 62 => ("📁", "Folder"), + 63 => ("📌", "Pin"), + _ => panic!("Trying to fetch an emoji outside the allowed range"), + } +} + +/// Get the extra info that will be used when we check the MAC of a +/// m.key.verification.key event. +/// +/// # Arguments +/// +/// * `ids` - The ids that are used for this SAS authentication flow. +/// +/// * `flow_id` - The unique id that identifies this SAS verification process. +fn extra_mac_info_receive(ids: &SasIds, flow_id: &str) -> String { + format!( + "MATRIX_KEY_VERIFICATION_MAC{first_user}{first_device}\ + {second_user}{second_device}{transaction_id}", + first_user = ids.other_device.user_id(), + first_device = ids.other_device.device_id(), + second_user = ids.account.user_id(), + second_device = ids.account.device_id(), + transaction_id = flow_id, + ) +} + +/// Get the content for a m.key.verification.mac event. +/// +/// Returns a tuple that contains the list of verified devices and the list of +/// verified master keys. +/// +/// # Arguments +/// +/// * `sas` - The Olm SAS object that can be used to MACs +/// +/// * `ids` - The ids that are used for this SAS authentication flow. +/// +/// * `flow_id` - The unique id that identifies this SAS verification process. +/// +/// * `event` - The m.key.verification.mac event that was sent to us by +/// the other side. +pub fn receive_mac_event( + sas: &OlmSas, + ids: &SasIds, + flow_id: &str, + event: &ToDeviceEvent, +) -> (Vec>, Vec) { + // TODO check the event and cancel if it isn't ok (sender, transaction id) + let mut verified_devices: Vec> = Vec::new(); + + let info = extra_mac_info_receive(&ids, flow_id); + + let mut keys = event.content.mac.keys().cloned().collect::>(); + keys.sort(); + let keys = sas + .calculate_mac(&keys.join(","), &format!("{}KEY_IDS", &info)) + .expect("Can't calculate SAS MAC"); + + if keys != event.content.keys { + panic!("Keys mac mismatch") + } + + for (key_id, key_mac) in &event.content.mac { + let split: Vec<&str> = key_id.splitn(2, ':').collect(); + + if split.len() != 2 { + continue; + } + + let algorithm: KeyAlgorithm = if let Ok(a) = split[0].try_into() { + a + } else { + continue; + }; + let id = split[1]; + + let device_key_id = AlgorithmAndDeviceId(algorithm, id.into()); + + if let Some(key) = ids.other_device.keys().get(&device_key_id) { + if key_mac + == &sas + .calculate_mac(key, &format!("{}{}", info, key_id)) + .expect("Can't calculate SAS MAC") + { + verified_devices.push(ids.other_device.device_id().into()); + } else { + // TODO cancel here + } + } + // TODO add an else branch for the master key here + } + + (verified_devices, vec![]) +} + +/// Get the extra info that will be used when we generate a MAC and need to send +/// it out +/// +/// # Arguments +/// +/// * `ids` - The ids that are used for this SAS authentication flow. +/// +/// * `flow_id` - The unique id that identifies this SAS verification process. +fn extra_mac_info_send(ids: &SasIds, flow_id: &str) -> String { + format!( + "MATRIX_KEY_VERIFICATION_MAC{first_user}{first_device}\ + {second_user}{second_device}{transaction_id}", + first_user = ids.account.user_id(), + first_device = ids.account.device_id(), + second_user = ids.other_device.user_id(), + second_device = ids.other_device.device_id(), + transaction_id = flow_id, + ) +} + +/// Get the content for a m.key.verification.mac event. +/// +/// # Arguments +/// +/// * `sas` - The Olm SAS object that can be used to generate the MAC +/// +/// * `ids` - The ids that are used for this SAS authentication flow. +/// +/// * `flow_id` - The unique id that identifies this SAS verification process. +/// +/// * `we_started` - Flag signaling if the SAS process was started on our side. +/// +/// # Panics +/// +/// This will panic if the public key of the other side wasn't set. +pub fn get_mac_content(sas: &OlmSas, ids: &SasIds, flow_id: &str) -> MacEventContent { + let mut mac: BTreeMap = BTreeMap::new(); + + let key_id = AlgorithmAndDeviceId(KeyAlgorithm::Ed25519, ids.account.device_id().into()); + let key = ids.account.identity_keys().ed25519(); + let info = extra_mac_info_send(ids, flow_id); + + mac.insert( + key_id.to_string(), + sas.calculate_mac(key, &format!("{}{}", info, key_id)) + .expect("Can't calculate SAS MAC"), + ); + + // TODO Add the cross signing master key here if we trust/have it. + + let mut keys = mac.keys().cloned().collect::>(); + keys.sort(); + let keys = sas + .calculate_mac(&keys.join(","), &format!("{}KEY_IDS", &info)) + .expect("Can't calculate SAS MAC"); + + MacEventContent { + transaction_id: flow_id.to_owned(), + keys, + mac, + } +} + +/// Get the extra info that will be used when we generate bytes for the short +/// auth string. +/// +/// # Arguments +/// +/// * `ids` - The ids that are used for this SAS authentication flow. +/// +/// * `flow_id` - The unique id that identifies this SAS verification process. +/// +/// * `we_started` - Flag signaling if the SAS process was started on our side. +fn extra_info_sas(ids: &SasIds, flow_id: &str, we_started: bool) -> String { + let (first_user, first_device, second_user, second_device) = if we_started { + ( + ids.account.user_id(), + ids.account.device_id(), + ids.other_device.user_id(), + ids.other_device.device_id(), + ) + } else { + ( + ids.other_device.user_id(), + ids.other_device.device_id(), + ids.account.user_id(), + ids.account.device_id(), + ) + }; + + format!( + "MATRIX_KEY_VERIFICATION_SAS{first_user}{first_device}\ + {second_user}{second_device}{transaction_id}", + first_user = first_user, + first_device = first_device, + second_user = second_user, + second_device = second_device, + transaction_id = flow_id, + ) +} + +/// Get the emoji version of the short authentication string. +/// +/// Returns a vector of tuples where the first element is the emoji and the +/// second element the English description of the emoji. +/// +/// # Arguments +/// +/// * `sas` - The Olm SAS object that can be used to generate bytes using the +/// shared secret. +/// +/// * `ids` - The ids that are used for this SAS authentication flow. +/// +/// * `flow_id` - The unique id that identifies this SAS verification process. +/// +/// * `we_started` - Flag signaling if the SAS process was started on our side. +/// +/// # Panics +/// +/// This will panic if the public key of the other side wasn't set. +pub fn get_emoji( + sas: &OlmSas, + ids: &SasIds, + flow_id: &str, + we_started: bool, +) -> Vec<(&'static str, &'static str)> { + let bytes: Vec = sas + .generate_bytes(&extra_info_sas(&ids, &flow_id, we_started), 6) + .expect("Can't generate bytes") + .into_iter() + .map(|b| b as u64) + .collect(); + + // Join the 6 bytes into one 64 bit unsigned int. This u64 will contain 48 + // bits from our 6 bytes. + let mut num: u64 = bytes[0] << 40; + num += bytes[1] << 32; + num += bytes[2] << 24; + num += bytes[3] << 16; + num += bytes[4] << 8; + num += bytes[5]; + + // Take the top 42 bits of our 48 bits from the u64 and convert each 6 bits + // into a 6 bit number. + let numbers = vec![ + ((num >> 42) & 63) as u8, + ((num >> 36) & 63) as u8, + ((num >> 30) & 63) as u8, + ((num >> 24) & 63) as u8, + ((num >> 18) & 63) as u8, + ((num >> 12) & 63) as u8, + ((num >> 6) & 63) as u8, + ]; + + // Convert the 6 bit number into a emoji/description tuple. + numbers.into_iter().map(emoji_from_index).collect() +} + +/// Get the decimal version of the short authentication string. +/// +/// Returns a tuple containing three 4 digit integer numbers that represent +/// the short auth string. +/// +/// # Arguments +/// +/// * `sas` - The Olm SAS object that can be used to generate bytes using the +/// shared secret. +/// +/// * `ids` - The ids that are used for this SAS authentication flow. +/// +/// * `flow_id` - The unique id that identifies this SAS verification process. +/// +/// * `we_started` - Flag signaling if the SAS process was started on our side. +/// +/// # Panics +/// +/// This will panic if the public key of the other side wasn't set. +pub fn get_decimal(sas: &OlmSas, ids: &SasIds, flow_id: &str, we_started: bool) -> (u32, u32, u32) { + let bytes: Vec = sas + .generate_bytes(&extra_info_sas(&ids, &flow_id, we_started), 5) + .expect("Can't generate bytes") + .into_iter() + .map(|b| b as u32) + .collect(); + + // This bitwise operation is taken from the [spec] + // [spec]: https://matrix.org/docs/spec/client_server/latest#sas-method-decimal + let first = bytes[0] << 5 | bytes[1] >> 3; + let second = (bytes[1] & 0x7) << 10 | bytes[2] << 2 | bytes[3] >> 6; + let third = (bytes[3] & 0x3F) << 7 | bytes[4] >> 1; + + (first + 1000, second + 1000, third + 1000) +} + +pub fn content_to_request( + recipient: &UserId, + recipient_device: &DeviceId, + content: AnyToDeviceEventContent, +) -> ToDeviceRequest { + let mut messages = BTreeMap::new(); + let mut user_messages = BTreeMap::new(); + + user_messages.insert( + DeviceIdOrAllDevices::DeviceId(recipient_device.into()), + serde_json::value::to_raw_value(&content).expect("Can't serialize to-device content"), + ); + messages.insert(recipient.clone(), user_messages); + + let event_type = match content { + AnyToDeviceEventContent::KeyVerificationAccept(_) => EventType::KeyVerificationAccept, + AnyToDeviceEventContent::KeyVerificationStart(_) => EventType::KeyVerificationStart, + AnyToDeviceEventContent::KeyVerificationKey(_) => EventType::KeyVerificationKey, + AnyToDeviceEventContent::KeyVerificationMac(_) => EventType::KeyVerificationMac, + AnyToDeviceEventContent::KeyVerificationCancel(_) => EventType::KeyVerificationCancel, + _ => unreachable!(), + }; + + ToDeviceRequest { + txn_id: Uuid::new_v4().to_string(), + event_type, + messages, + } +} diff --git a/matrix_sdk_crypto/src/verification/sas/mod.rs b/matrix_sdk_crypto/src/verification/sas/mod.rs new file mode 100644 index 00000000..1e0c5ac6 --- /dev/null +++ b/matrix_sdk_crypto/src/verification/sas/mod.rs @@ -0,0 +1,589 @@ +// Copyright 2020 The Matrix.org Foundation C.I.C. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +mod helpers; +mod sas_state; + +use std::sync::{Arc, Mutex}; + +use matrix_sdk_common::{ + api::r0::to_device::send_event_to_device::Request as ToDeviceRequest, + events::{ + key::verification::{ + accept::AcceptEventContent, cancel::CancelCode, mac::MacEventContent, + start::StartEventContent, + }, + AnyToDeviceEvent, AnyToDeviceEventContent, ToDeviceEvent, + }, + identifiers::{DeviceId, UserId}, +}; + +use crate::{Account, Device}; +pub use helpers::content_to_request; +use sas_state::{ + Accepted, Canceled, Confirmed, Created, Done, KeyReceived, MacReceived, SasState, Started, +}; + +#[derive(Clone, Debug)] +/// Short authentication string object. +pub struct Sas { + inner: Arc>, + account: Account, + other_device: Device, + flow_id: Arc, +} + +impl Sas { + /// Get our own user id. + pub fn user_id(&self) -> &UserId { + self.account.user_id() + } + + /// Get our own device id. + pub fn device_id(&self) -> &DeviceId { + self.account.device_id() + } + + /// Get the user id of the other side. + pub fn other_user_id(&self) -> &UserId { + self.other_device.user_id() + } + + /// Get the device id of the other side. + pub fn other_device_id(&self) -> &DeviceId { + self.other_device.device_id() + } + + /// Get the unique ID that identifies this SAS verification flow. + pub fn flow_id(&self) -> &str { + &self.flow_id + } + + /// Start a new SAS auth flow with the given device. + /// + /// # Arguments + /// + /// * `account` - Our own account. + /// + /// * `other_device` - The other device which we are going to verify. + /// + /// Returns the new `Sas` object and a `StartEventContent` that needs to be + /// sent out through the server to the other device. + pub(crate) fn start(account: Account, other_device: Device) -> (Sas, StartEventContent) { + let (inner, content) = InnerSas::start(account.clone(), other_device.clone()); + let flow_id = inner.verification_flow_id(); + + let sas = Sas { + inner: Arc::new(Mutex::new(inner)), + account, + other_device, + flow_id, + }; + + (sas, content) + } + + /// Create a new Sas object from a m.key.verification.start request. + /// + /// # Arguments + /// + /// * `account` - Our own account. + /// + /// * `other_device` - The other device which we are going to verify. + /// + /// * `event` - The m.key.verification.start event that was sent to us by + /// the other side. + pub(crate) fn from_start_event( + account: Account, + other_device: Device, + event: &ToDeviceEvent, + ) -> Result { + let inner = InnerSas::from_start_event(account.clone(), other_device.clone(), event)?; + let flow_id = inner.verification_flow_id(); + Ok(Sas { + inner: Arc::new(Mutex::new(inner)), + account, + other_device, + flow_id, + }) + } + + /// Accept the SAS verification. + /// + /// This does nothing if the verification was already accepted, otherwise it + /// returns an `AcceptEventContent` that needs to be sent out. + pub fn accept(&self) -> Option { + self.inner.lock().unwrap().accept().map(|c| { + let content = AnyToDeviceEventContent::KeyVerificationAccept(c); + self.content_to_request(content) + }) + } + + /// Confirm the Sas verification. + /// + /// This confirms that the short auth strings match on both sides. + /// + /// Does nothing if we're not in a state where we can confirm the short auth + /// string, otherwise returns a `MacEventContent` that needs to be sent to + /// the server. + pub fn confirm(&self) -> Option { + let mut guard = self.inner.lock().unwrap(); + let sas: InnerSas = (*guard).clone(); + let (sas, content) = sas.confirm(); + *guard = sas; + + content.map(|c| { + let content = AnyToDeviceEventContent::KeyVerificationMac(c); + self.content_to_request(content) + }) + } + + /// Cancel the verification. + /// + /// This cancels the verification with the `CancelCode::User`. + /// + /// Returns None if the `Sas` object is already in a canceled state, + /// otherwise it returns a request that needs to be sent out. + pub fn cancel(&self) -> Option { + let mut guard = self.inner.lock().unwrap(); + let sas: InnerSas = (*guard).clone(); + let (sas, content) = sas.cancel(); + *guard = sas; + + content.map(|c| self.content_to_request(c)) + } + + /// Are we in a state where we can show the short auth string. + pub fn can_be_presented(&self) -> bool { + self.inner.lock().unwrap().can_be_presented() + } + + /// Is the SAS flow done. + pub fn is_done(&self) -> bool { + self.inner.lock().unwrap().is_done() + } + + /// Get the emoji version of the short auth string. + /// + /// Returns None if we can't yet present the short auth string, otherwise a + /// Vec of tuples with the emoji and description. + pub fn emoji(&self) -> Option> { + self.inner.lock().unwrap().emoji() + } + + /// Get the decimal version of the short auth string. + /// + /// Returns None if we can't yet present the short auth string, otherwise a + /// tuple containing three 4-digit integers that represent the short auth + /// string. + pub fn decimals(&self) -> Option<(u32, u32, u32)> { + self.inner.lock().unwrap().decimals() + } + + pub(crate) fn receive_event( + &self, + event: &mut AnyToDeviceEvent, + ) -> Option { + let mut guard = self.inner.lock().unwrap(); + let sas: InnerSas = (*guard).clone(); + let (sas, content) = sas.receive_event(event); + *guard = sas; + + content + } + + pub(crate) fn verified_devices(&self) -> Option>>> { + self.inner.lock().unwrap().verified_devices() + } + + pub(crate) fn content_to_request(&self, content: AnyToDeviceEventContent) -> ToDeviceRequest { + content_to_request(self.other_user_id(), self.other_device_id(), content) + } +} + +#[derive(Clone, Debug)] +enum InnerSas { + Created(SasState), + Started(SasState), + Accepted(SasState), + KeyRecieved(SasState), + Confirmed(SasState), + MacReceived(SasState), + Done(SasState), + Canceled(SasState), +} + +impl InnerSas { + fn start(account: Account, other_device: Device) -> (InnerSas, StartEventContent) { + let sas = SasState::::new(account, other_device); + let content = sas.as_content(); + (InnerSas::Created(sas), content) + } + + fn from_start_event( + account: Account, + other_device: Device, + event: &ToDeviceEvent, + ) -> Result { + match SasState::::from_start_event(account, other_device, event) { + Ok(s) => Ok(InnerSas::Started(s)), + Err(s) => Err(s.as_content()), + } + } + + fn accept(&self) -> Option { + if let InnerSas::Started(s) = self { + Some(s.as_content()) + } else { + None + } + } + + fn cancel(self) -> (InnerSas, Option) { + let sas = match self { + InnerSas::Created(s) => s.cancel(CancelCode::User), + InnerSas::Started(s) => s.cancel(CancelCode::User), + InnerSas::Accepted(s) => s.cancel(CancelCode::User), + InnerSas::KeyRecieved(s) => s.cancel(CancelCode::User), + InnerSas::MacReceived(s) => s.cancel(CancelCode::User), + _ => return (self, None), + }; + + let content = sas.as_content(); + + (InnerSas::Canceled(sas), Some(content)) + } + + fn confirm(self) -> (InnerSas, Option) { + match self { + InnerSas::KeyRecieved(s) => { + let sas = s.confirm(); + let content = sas.as_content(); + (InnerSas::Confirmed(sas), Some(content)) + } + InnerSas::MacReceived(s) => { + let sas = s.confirm(); + let content = sas.as_content(); + (InnerSas::Done(sas), Some(content)) + } + _ => (self, None), + } + } + + fn receive_event( + self, + event: &mut AnyToDeviceEvent, + ) -> (InnerSas, Option) { + match event { + AnyToDeviceEvent::KeyVerificationAccept(e) => { + if let InnerSas::Created(s) = self { + match s.into_accepted(e) { + Ok(s) => { + let content = s.as_content(); + ( + InnerSas::Accepted(s), + Some(AnyToDeviceEventContent::KeyVerificationKey(content)), + ) + } + Err(s) => { + let content = s.as_content(); + (InnerSas::Canceled(s), Some(content)) + } + } + } else { + (self, None) + } + } + AnyToDeviceEvent::KeyVerificationKey(e) => match self { + InnerSas::Accepted(s) => match s.into_key_received(e) { + Ok(s) => (InnerSas::KeyRecieved(s), None), + Err(s) => { + let content = s.as_content(); + (InnerSas::Canceled(s), Some(content)) + } + }, + InnerSas::Started(s) => match s.into_key_received(e) { + Ok(s) => { + let content = s.as_content(); + ( + InnerSas::KeyRecieved(s), + Some(AnyToDeviceEventContent::KeyVerificationKey(content)), + ) + } + Err(s) => { + let content = s.as_content(); + (InnerSas::Canceled(s), Some(content)) + } + }, + _ => (self, None), + }, + AnyToDeviceEvent::KeyVerificationMac(e) => match self { + InnerSas::KeyRecieved(s) => match s.into_mac_received(e) { + Ok(s) => (InnerSas::MacReceived(s), None), + Err(s) => { + let content = s.as_content(); + (InnerSas::Canceled(s), Some(content)) + } + }, + InnerSas::Confirmed(s) => match s.into_done(e) { + Ok(s) => (InnerSas::Done(s), None), + Err(s) => { + let content = s.as_content(); + (InnerSas::Canceled(s), Some(content)) + } + }, + _ => (self, None), + }, + _ => (self, None), + } + } + + fn can_be_presented(&self) -> bool { + match self { + InnerSas::KeyRecieved(_) => true, + InnerSas::MacReceived(_) => true, + _ => false, + } + } + + fn is_done(&self) -> bool { + if let InnerSas::Done(_) = self { + true + } else { + false + } + } + + fn verification_flow_id(&self) -> Arc { + match self { + InnerSas::Created(s) => s.verification_flow_id.clone(), + InnerSas::Started(s) => s.verification_flow_id.clone(), + InnerSas::Canceled(s) => s.verification_flow_id.clone(), + InnerSas::Accepted(s) => s.verification_flow_id.clone(), + InnerSas::KeyRecieved(s) => s.verification_flow_id.clone(), + InnerSas::Confirmed(s) => s.verification_flow_id.clone(), + InnerSas::MacReceived(s) => s.verification_flow_id.clone(), + InnerSas::Done(s) => s.verification_flow_id.clone(), + } + } + + fn emoji(&self) -> Option> { + match self { + InnerSas::KeyRecieved(s) => Some(s.get_emoji()), + InnerSas::MacReceived(s) => Some(s.get_emoji()), + _ => None, + } + } + + fn decimals(&self) -> Option<(u32, u32, u32)> { + match self { + InnerSas::KeyRecieved(s) => Some(s.get_decimal()), + InnerSas::MacReceived(s) => Some(s.get_decimal()), + _ => None, + } + } + + fn verified_devices(&self) -> Option>>> { + if let InnerSas::Done(s) = self { + Some(s.verified_devices()) + } else { + None + } + } + + fn verified_master_keys(&self) -> Option>> { + if let InnerSas::Done(s) = self { + Some(s.verified_master_keys()) + } else { + None + } + } +} + +#[cfg(test)] +mod test { + use std::convert::TryFrom; + + use matrix_sdk_common::events::{EventContent, ToDeviceEvent}; + use matrix_sdk_common::identifiers::{DeviceId, UserId}; + + use crate::{ + verification::test::{get_content_from_request, wrap_any_to_device_content}, + Account, Device, + }; + + use super::{Accepted, Created, Sas, SasState, Started}; + + fn alice_id() -> UserId { + UserId::try_from("@alice:example.org").unwrap() + } + + fn alice_device_id() -> Box { + "JLAFKJWSCS".into() + } + + fn bob_id() -> UserId { + UserId::try_from("@bob:example.org").unwrap() + } + + fn bob_device_id() -> Box { + "BOBDEVCIE".into() + } + + fn wrap_to_device_event(sender: &UserId, content: C) -> ToDeviceEvent { + ToDeviceEvent { + sender: sender.clone(), + content, + } + } + + async fn get_sas_pair() -> (SasState, SasState) { + let alice = Account::new(&alice_id(), &alice_device_id()); + let alice_device = Device::from_account(&alice).await; + + let bob = Account::new(&bob_id(), &bob_device_id()); + let bob_device = Device::from_account(&bob).await; + + let alice_sas = SasState::::new(alice.clone(), bob_device); + + let start_content = alice_sas.as_content(); + let event = wrap_to_device_event(alice_sas.user_id(), start_content); + + let bob_sas = SasState::::from_start_event(bob.clone(), alice_device, &event); + + (alice_sas, bob_sas.unwrap()) + } + + #[tokio::test] + async fn create_sas() { + let (_, _) = get_sas_pair().await; + } + + #[tokio::test] + async fn sas_accept() { + let (alice, bob) = get_sas_pair().await; + + let event = wrap_to_device_event(bob.user_id(), bob.as_content()); + + alice.into_accepted(&event).unwrap(); + } + + #[tokio::test] + async fn sas_key_share() { + let (alice, bob) = get_sas_pair().await; + + let event = wrap_to_device_event(bob.user_id(), bob.as_content()); + + let alice: SasState = alice.into_accepted(&event).unwrap(); + let mut event = wrap_to_device_event(alice.user_id(), alice.as_content()); + + let bob = bob.into_key_received(&mut event).unwrap(); + + let mut event = wrap_to_device_event(bob.user_id(), bob.as_content()); + + let alice = alice.into_key_received(&mut event).unwrap(); + + assert_eq!(alice.get_decimal(), bob.get_decimal()); + assert_eq!(alice.get_emoji(), bob.get_emoji()); + } + + #[tokio::test] + async fn sas_full() { + let (alice, bob) = get_sas_pair().await; + + let event = wrap_to_device_event(bob.user_id(), bob.as_content()); + + let alice: SasState = alice.into_accepted(&event).unwrap(); + let mut event = wrap_to_device_event(alice.user_id(), alice.as_content()); + + let bob = bob.into_key_received(&mut event).unwrap(); + + let mut event = wrap_to_device_event(bob.user_id(), bob.as_content()); + + let alice = alice.into_key_received(&mut event).unwrap(); + + assert_eq!(alice.get_decimal(), bob.get_decimal()); + assert_eq!(alice.get_emoji(), bob.get_emoji()); + + let bob = bob.confirm(); + + let event = wrap_to_device_event(bob.user_id(), bob.as_content()); + + let alice = alice.into_mac_received(&event).unwrap(); + assert!(!alice.get_emoji().is_empty()); + let alice = alice.confirm(); + + let event = wrap_to_device_event(alice.user_id(), alice.as_content()); + let bob = bob.into_done(&event).unwrap(); + + assert!(bob.verified_devices().contains(&alice.device_id().into())); + assert!(alice.verified_devices().contains(&bob.device_id().into())); + } + + #[tokio::test] + async fn sas_wrapper_full() { + let alice = Account::new(&alice_id(), &alice_device_id()); + let alice_device = Device::from_account(&alice).await; + + let bob = Account::new(&bob_id(), &bob_device_id()); + let bob_device = Device::from_account(&bob).await; + + let (alice, content) = Sas::start(alice, bob_device); + let event = wrap_to_device_event(alice.user_id(), content); + + let bob = Sas::from_start_event(bob, alice_device, &event).unwrap(); + let mut event = wrap_any_to_device_content( + bob.user_id(), + get_content_from_request(&bob.accept().unwrap()), + ); + + let content = alice.receive_event(&mut event); + + assert!(!alice.can_be_presented()); + assert!(!bob.can_be_presented()); + + let mut event = wrap_any_to_device_content(alice.user_id(), content.unwrap()); + let mut event = + wrap_any_to_device_content(bob.user_id(), bob.receive_event(&mut event).unwrap()); + + assert!(bob.can_be_presented()); + + alice.receive_event(&mut event); + assert!(alice.can_be_presented()); + + assert_eq!(alice.emoji().unwrap(), bob.emoji().unwrap()); + assert_eq!(alice.decimals().unwrap(), bob.decimals().unwrap()); + + let mut event = wrap_any_to_device_content( + alice.user_id(), + get_content_from_request(&alice.confirm().unwrap()), + ); + bob.receive_event(&mut event); + + let mut event = wrap_any_to_device_content( + bob.user_id(), + get_content_from_request(&bob.confirm().unwrap()), + ); + alice.receive_event(&mut event); + + assert!(alice + .verified_devices() + .unwrap() + .contains(&bob.device_id().into())); + assert!(bob + .verified_devices() + .unwrap() + .contains(&alice.device_id().into())); + } +} diff --git a/matrix_sdk_crypto/src/verification/sas.rs b/matrix_sdk_crypto/src/verification/sas/sas_state.rs similarity index 60% rename from matrix_sdk_crypto/src/verification/sas.rs rename to matrix_sdk_crypto/src/verification/sas/sas_state.rs index 14ef7a98..61573cd1 100644 --- a/matrix_sdk_crypto/src/verification/sas.rs +++ b/matrix_sdk_crypto/src/verification/sas/sas_state.rs @@ -20,7 +20,6 @@ use std::{ use olm_rs::{sas::OlmSas, utility::OlmUtility}; use matrix_sdk_common::{ - api::r0::to_device::send_event_to_device::Request as ToDeviceRequest, events::{ key::verification::{ accept::{ @@ -34,401 +33,24 @@ use matrix_sdk_common::{ HashAlgorithm, KeyAgreementProtocol, MessageAuthenticationCode, ShortAuthenticationString, VerificationMethod, }, - AnyToDeviceEvent, AnyToDeviceEventContent, ToDeviceEvent, + AnyToDeviceEventContent, ToDeviceEvent, }, identifiers::{DeviceId, UserId}, uuid::Uuid, }; -use super::{ - content_to_request, get_decimal, get_emoji, get_mac_content, receive_mac_event, SasIds, -}; +use super::helpers::{get_decimal, get_emoji, get_mac_content, receive_mac_event, SasIds}; + use crate::{Account, Device}; -#[derive(Clone, Debug)] -/// Short authentication string object. -pub struct Sas { - inner: Arc>, - account: Account, - other_device: Device, - flow_id: Arc, -} - -impl Sas { - const KEY_AGREEMENT_PROTOCOLS: &'static [KeyAgreementProtocol] = - &[KeyAgreementProtocol::Curve25519HkdfSha256]; - const HASHES: &'static [HashAlgorithm] = &[HashAlgorithm::Sha256]; - const MACS: &'static [MessageAuthenticationCode] = &[MessageAuthenticationCode::HkdfHmacSha256]; - const STRINGS: &'static [ShortAuthenticationString] = &[ - ShortAuthenticationString::Decimal, - ShortAuthenticationString::Emoji, - ]; - - /// Get our own user id. - pub fn user_id(&self) -> &UserId { - self.account.user_id() - } - - /// Get our own device id. - pub fn device_id(&self) -> &DeviceId { - self.account.device_id() - } - - /// Get the user id of the other side. - pub fn other_user_id(&self) -> &UserId { - self.other_device.user_id() - } - - /// Get the device id of the other side. - pub fn other_device_id(&self) -> &DeviceId { - self.other_device.device_id() - } - - /// Get the unique ID that identifies this SAS verification flow. - pub fn flow_id(&self) -> &str { - &self.flow_id - } - - /// Start a new SAS auth flow with the given device. - /// - /// # Arguments - /// - /// * `account` - Our own account. - /// - /// * `other_device` - The other device which we are going to verify. - /// - /// Returns the new `Sas` object and a `StartEventContent` that needs to be - /// sent out through the server to the other device. - pub(crate) fn start(account: Account, other_device: Device) -> (Sas, StartEventContent) { - let (inner, content) = InnerSas::start(account.clone(), other_device.clone()); - let flow_id = inner.verification_flow_id(); - - let sas = Sas { - inner: Arc::new(Mutex::new(inner)), - account, - other_device, - flow_id, - }; - - (sas, content) - } - - /// Create a new Sas object from a m.key.verification.start request. - /// - /// # Arguments - /// - /// * `account` - Our own account. - /// - /// * `other_device` - The other device which we are going to verify. - /// - /// * `event` - The m.key.verification.start event that was sent to us by - /// the other side. - pub(crate) fn from_start_event( - account: Account, - other_device: Device, - event: &ToDeviceEvent, - ) -> Result { - let inner = InnerSas::from_start_event(account.clone(), other_device.clone(), event)?; - let flow_id = inner.verification_flow_id(); - Ok(Sas { - inner: Arc::new(Mutex::new(inner)), - account, - other_device, - flow_id, - }) - } - - /// Accept the SAS verification. - /// - /// This does nothing if the verification was already accepted, otherwise it - /// returns an `AcceptEventContent` that needs to be sent out. - pub fn accept(&self) -> Option { - self.inner.lock().unwrap().accept().map(|c| { - let content = AnyToDeviceEventContent::KeyVerificationAccept(c); - self.content_to_request(content) - }) - } - - /// Confirm the Sas verification. - /// - /// This confirms that the short auth strings match on both sides. - /// - /// Does nothing if we're not in a state where we can confirm the short auth - /// string, otherwise returns a `MacEventContent` that needs to be sent to - /// the server. - pub fn confirm(&self) -> Option { - let mut guard = self.inner.lock().unwrap(); - let sas: InnerSas = (*guard).clone(); - let (sas, content) = sas.confirm(); - *guard = sas; - - content.map(|c| { - let content = AnyToDeviceEventContent::KeyVerificationMac(c); - self.content_to_request(content) - }) - } - - /// Cancel the verification. - /// - /// This cancels the verification with the `CancelCode::User`. - /// - /// Returns None if the `Sas` object is already in a canceled state, - /// otherwise it returns a request that needs to be sent out. - pub fn cancel(&self) -> Option { - let mut guard = self.inner.lock().unwrap(); - let sas: InnerSas = (*guard).clone(); - let (sas, content) = sas.cancel(); - *guard = sas; - - content.map(|c| self.content_to_request(c)) - } - - /// Are we in a state where we can show the short auth string. - pub fn can_be_presented(&self) -> bool { - self.inner.lock().unwrap().can_be_presented() - } - - /// Is the SAS flow done. - pub fn is_done(&self) -> bool { - self.inner.lock().unwrap().is_done() - } - - /// Get the emoji version of the short auth string. - /// - /// Returns None if we can't yet present the short auth string, otherwise a - /// Vec of tuples with the emoji and description. - pub fn emoji(&self) -> Option> { - self.inner.lock().unwrap().emoji() - } - - /// Get the decimal version of the short auth string. - /// - /// Returns None if we can't yet present the short auth string, otherwise a - /// tuple containing three 4-digit integers that represent the short auth - /// string. - pub fn decimals(&self) -> Option<(u32, u32, u32)> { - self.inner.lock().unwrap().decimals() - } - - pub(crate) fn receive_event( - &self, - event: &mut AnyToDeviceEvent, - ) -> Option { - let mut guard = self.inner.lock().unwrap(); - let sas: InnerSas = (*guard).clone(); - let (sas, content) = sas.receive_event(event); - *guard = sas; - - content - } - - pub(crate) fn verified_devices(&self) -> Option>>> { - self.inner.lock().unwrap().verified_devices() - } - - pub(crate) fn content_to_request(&self, content: AnyToDeviceEventContent) -> ToDeviceRequest { - content_to_request(self.other_user_id(), self.other_device_id(), content) - } -} - -#[derive(Clone, Debug)] -enum InnerSas { - Created(SasState), - Started(SasState), - Accepted(SasState), - KeyRecieved(SasState), - Confirmed(SasState), - MacReceived(SasState), - Done(SasState), - Canceled(SasState), -} - -impl InnerSas { - fn start(account: Account, other_device: Device) -> (InnerSas, StartEventContent) { - let sas = SasState::::new(account, other_device); - let content = sas.as_content(); - (InnerSas::Created(sas), content) - } - - fn from_start_event( - account: Account, - other_device: Device, - event: &ToDeviceEvent, - ) -> Result { - match SasState::::from_start_event(account, other_device, event) { - Ok(s) => Ok(InnerSas::Started(s)), - Err(s) => Err(s.as_content()), - } - } - - fn accept(&self) -> Option { - if let InnerSas::Started(s) = self { - Some(s.as_content()) - } else { - None - } - } - - fn cancel(self) -> (InnerSas, Option) { - let sas = match self { - InnerSas::Created(s) => s.cancel(CancelCode::User), - InnerSas::Started(s) => s.cancel(CancelCode::User), - InnerSas::Accepted(s) => s.cancel(CancelCode::User), - InnerSas::KeyRecieved(s) => s.cancel(CancelCode::User), - InnerSas::MacReceived(s) => s.cancel(CancelCode::User), - _ => return (self, None), - }; - - let content = sas.as_content(); - - (InnerSas::Canceled(sas), Some(content)) - } - - fn confirm(self) -> (InnerSas, Option) { - match self { - InnerSas::KeyRecieved(s) => { - let sas = s.confirm(); - let content = sas.as_content(); - (InnerSas::Confirmed(sas), Some(content)) - } - InnerSas::MacReceived(s) => { - let sas = s.confirm(); - let content = sas.as_content(); - (InnerSas::Done(sas), Some(content)) - } - _ => (self, None), - } - } - - fn receive_event( - self, - event: &mut AnyToDeviceEvent, - ) -> (InnerSas, Option) { - match event { - AnyToDeviceEvent::KeyVerificationAccept(e) => { - if let InnerSas::Created(s) = self { - match s.into_accepted(e) { - Ok(s) => { - let content = s.as_content(); - ( - InnerSas::Accepted(s), - Some(AnyToDeviceEventContent::KeyVerificationKey(content)), - ) - } - Err(s) => { - let content = s.as_content(); - (InnerSas::Canceled(s), Some(content)) - } - } - } else { - (self, None) - } - } - AnyToDeviceEvent::KeyVerificationKey(e) => match self { - InnerSas::Accepted(s) => match s.into_key_received(e) { - Ok(s) => (InnerSas::KeyRecieved(s), None), - Err(s) => { - let content = s.as_content(); - (InnerSas::Canceled(s), Some(content)) - } - }, - InnerSas::Started(s) => match s.into_key_received(e) { - Ok(s) => { - let content = s.as_content(); - ( - InnerSas::KeyRecieved(s), - Some(AnyToDeviceEventContent::KeyVerificationKey(content)), - ) - } - Err(s) => { - let content = s.as_content(); - (InnerSas::Canceled(s), Some(content)) - } - }, - _ => (self, None), - }, - AnyToDeviceEvent::KeyVerificationMac(e) => match self { - InnerSas::KeyRecieved(s) => match s.into_mac_received(e) { - Ok(s) => (InnerSas::MacReceived(s), None), - Err(s) => { - let content = s.as_content(); - (InnerSas::Canceled(s), Some(content)) - } - }, - InnerSas::Confirmed(s) => match s.into_done(e) { - Ok(s) => (InnerSas::Done(s), None), - Err(s) => { - let content = s.as_content(); - (InnerSas::Canceled(s), Some(content)) - } - }, - _ => (self, None), - }, - _ => (self, None), - } - } - - fn can_be_presented(&self) -> bool { - match self { - InnerSas::KeyRecieved(_) => true, - InnerSas::MacReceived(_) => true, - _ => false, - } - } - - fn is_done(&self) -> bool { - if let InnerSas::Done(_) = self { - true - } else { - false - } - } - - fn verification_flow_id(&self) -> Arc { - match self { - InnerSas::Created(s) => s.verification_flow_id.clone(), - InnerSas::Started(s) => s.verification_flow_id.clone(), - InnerSas::Canceled(s) => s.verification_flow_id.clone(), - InnerSas::Accepted(s) => s.verification_flow_id.clone(), - InnerSas::KeyRecieved(s) => s.verification_flow_id.clone(), - InnerSas::Confirmed(s) => s.verification_flow_id.clone(), - InnerSas::MacReceived(s) => s.verification_flow_id.clone(), - InnerSas::Done(s) => s.verification_flow_id.clone(), - } - } - - fn emoji(&self) -> Option> { - match self { - InnerSas::KeyRecieved(s) => Some(s.get_emoji()), - InnerSas::MacReceived(s) => Some(s.get_emoji()), - _ => None, - } - } - - fn decimals(&self) -> Option<(u32, u32, u32)> { - match self { - InnerSas::KeyRecieved(s) => Some(s.get_decimal()), - InnerSas::MacReceived(s) => Some(s.get_decimal()), - _ => None, - } - } - - fn verified_devices(&self) -> Option>>> { - if let InnerSas::Done(s) = self { - Some(s.verified_devices()) - } else { - None - } - } - - fn verified_master_keys(&self) -> Option>> { - if let InnerSas::Done(s) = self { - Some(s.verified_master_keys()) - } else { - None - } - } -} +const KEY_AGREEMENT_PROTOCOLS: &'static [KeyAgreementProtocol] = + &[KeyAgreementProtocol::Curve25519HkdfSha256]; +const HASHES: &'static [HashAlgorithm] = &[HashAlgorithm::Sha256]; +const MACS: &'static [MessageAuthenticationCode] = &[MessageAuthenticationCode::HkdfHmacSha256]; +const STRINGS: &'static [ShortAuthenticationString] = &[ + ShortAuthenticationString::Decimal, + ShortAuthenticationString::Emoji, +]; /// Struct containing the protocols that were agreed to be used for the SAS /// flow. @@ -473,7 +95,7 @@ impl Default for AcceptedProtocols { /// This is the generic struc holding common data between the different states /// and the specific state. #[derive(Clone)] -struct SasState { +pub struct SasState { /// The Olm SAS struct. inner: Arc>, /// Struct holding the identities that are doing the SAS dance. @@ -482,7 +104,7 @@ struct SasState { /// /// This will be the transaction id for to-device events and the relates_to /// field for in-room events. - verification_flow_id: Arc, + pub verification_flow_id: Arc, /// The SAS state we're in. state: Arc, } @@ -499,13 +121,13 @@ impl std::fmt::Debug for SasState { /// The initial SAS state. #[derive(Clone, Debug)] -struct Created { +pub struct Created { protocol_definitions: MSasV1ContentInit, } /// The initial SAS state if the other side started the SAS verification. #[derive(Clone, Debug)] -struct Started { +pub struct Started { commitment: String, protocol_definitions: MSasV1Content, } @@ -513,7 +135,7 @@ struct Started { /// The SAS state we're going to be in after the other side accepted our /// verification start event. #[derive(Clone, Debug)] -struct Accepted { +pub struct Accepted { accepted_protocols: Arc, json_start_content: String, commitment: String, @@ -524,7 +146,7 @@ struct Accepted { /// /// From now on we can show the short auth string to the user. #[derive(Clone, Debug)] -struct KeyReceived { +pub struct KeyReceived { we_started: bool, accepted_protocols: Arc, } @@ -533,7 +155,7 @@ struct KeyReceived { /// short auth string matches. We still need to receive a MAC event from the /// other side. #[derive(Clone, Debug)] -struct Confirmed { +pub struct Confirmed { accepted_protocols: Arc, } @@ -541,7 +163,7 @@ struct Confirmed { /// other side. Our own user still needs to confirm that the short auth string /// matches. #[derive(Clone, Debug)] -struct MacReceived { +pub struct MacReceived { we_started: bool, verified_devices: Arc>>, verified_master_keys: Arc>, @@ -552,29 +174,29 @@ struct MacReceived { /// We can now mark the device in our verified devices lits as verified and sign /// the master keys in the verified devices list. #[derive(Clone, Debug)] -struct Done { +pub struct Done { verified_devices: Arc>>, verified_master_keys: Arc>, } #[derive(Clone, Debug)] -struct Canceled { +pub struct Canceled { cancel_code: CancelCode, reason: &'static str, } impl SasState { /// Get our own user id. - fn user_id(&self) -> &UserId { + pub fn user_id(&self) -> &UserId { &self.ids.account.user_id() } /// Get our own device id. - fn device_id(&self) -> &DeviceId { + pub fn device_id(&self) -> &DeviceId { &self.ids.account.device_id() } - fn cancel(self, cancel_code: CancelCode) -> SasState { + pub fn cancel(self, cancel_code: CancelCode) -> SasState { SasState { inner: self.inner, ids: self.ids, @@ -602,7 +224,7 @@ impl SasState { /// * `account` - Our own account. /// /// * `other_device` - The other device which we are going to verify. - fn new(account: Account, other_device: Device) -> SasState { + pub fn new(account: Account, other_device: Device) -> SasState { let verification_flow_id = Uuid::new_v4().to_string(); SasState { @@ -615,10 +237,10 @@ impl SasState { state: Arc::new(Created { protocol_definitions: MSasV1ContentInit { - short_authentication_string: Sas::STRINGS.to_vec(), - key_agreement_protocols: Sas::KEY_AGREEMENT_PROTOCOLS.to_vec(), - message_authentication_codes: Sas::MACS.to_vec(), - hashes: Sas::HASHES.to_vec(), + short_authentication_string: STRINGS.to_vec(), + key_agreement_protocols: KEY_AGREEMENT_PROTOCOLS.to_vec(), + message_authentication_codes: MACS.to_vec(), + hashes: HASHES.to_vec(), }, }), } @@ -627,7 +249,7 @@ impl SasState { /// Get the content for the start event. /// /// The content needs to be sent to the other device. - fn as_content(&self) -> StartEventContent { + pub fn as_content(&self) -> StartEventContent { StartEventContent { transaction_id: self.verification_flow_id.to_string(), from_device: self.device_id().into(), @@ -645,7 +267,7 @@ impl SasState { /// /// * `event` - The m.key.verification.accept event that was sent to us by /// the other side. - fn into_accepted( + pub fn into_accepted( self, event: &ToDeviceEvent, ) -> Result, SasState> { @@ -653,9 +275,9 @@ impl SasState { .map_err(|c| self.clone().cancel(c))?; if let AcceptMethod::MSasV1(content) = &event.content.method { - if !Sas::KEY_AGREEMENT_PROTOCOLS.contains(&content.key_agreement_protocol) - || !Sas::HASHES.contains(&content.hash) - || !Sas::MACS.contains(&content.message_authentication_code) + if !KEY_AGREEMENT_PROTOCOLS.contains(&content.key_agreement_protocol) + || !HASHES.contains(&content.hash) + || !MACS.contains(&content.message_authentication_code) || (!content .short_authentication_string .contains(&ShortAuthenticationString::Emoji) @@ -699,7 +321,7 @@ impl SasState { /// /// * `event` - The m.key.verification.start event that was sent to us by /// the other side. - fn from_start_event( + pub fn from_start_event( account: Account, other_device: Device, event: &ToDeviceEvent, @@ -768,7 +390,7 @@ impl SasState { /// This should be sent out automatically if the SAS verification flow has /// been started because of a /// m.key.verification.request -> m.key.verification.ready flow. - fn as_content(&self) -> AcceptEventContent { + pub fn as_content(&self) -> AcceptEventContent { let accepted_protocols = AcceptedProtocols::default(); AcceptEventContent { @@ -798,7 +420,7 @@ impl SasState { /// * `event` - The m.key.verification.key event that was sent to us by /// the other side. The event will be modified so it doesn't contain any key /// anymore. - fn into_key_received( + pub fn into_key_received( self, event: &mut ToDeviceEvent, ) -> Result, SasState> { @@ -834,7 +456,7 @@ impl SasState { /// * `event` - The m.key.verification.key event that was sent to us by /// the other side. The event will be modified so it doesn't contain any key /// anymore. - fn into_key_received( + pub fn into_key_received( self, event: &mut ToDeviceEvent, ) -> Result, SasState> { @@ -871,7 +493,7 @@ impl SasState { /// Get the content for the key event. /// /// The content needs to be automatically sent to the other side. - fn as_content(&self) -> KeyEventContent { + pub fn as_content(&self) -> KeyEventContent { KeyEventContent { transaction_id: self.verification_flow_id.to_string(), key: self.inner.lock().unwrap().public_key(), @@ -884,7 +506,7 @@ impl SasState { /// /// The content needs to be automatically sent to the other side if and only /// if we_started is false. - fn as_content(&self) -> KeyEventContent { + pub fn as_content(&self) -> KeyEventContent { KeyEventContent { transaction_id: self.verification_flow_id.to_string(), key: self.inner.lock().unwrap().public_key(), @@ -895,7 +517,7 @@ impl SasState { /// /// Returns a vector of tuples where the first element is the emoji and the /// second element the English description of the emoji. - fn get_emoji(&self) -> Vec<(&'static str, &'static str)> { + pub fn get_emoji(&self) -> Vec<(&'static str, &'static str)> { get_emoji( &self.inner.lock().unwrap(), &self.ids, @@ -908,7 +530,7 @@ impl SasState { /// /// Returns a tuple containing three 4 digit integer numbers that represent /// the short auth string. - fn get_decimal(&self) -> (u32, u32, u32) { + pub fn get_decimal(&self) -> (u32, u32, u32) { get_decimal( &self.inner.lock().unwrap(), &self.ids, @@ -924,7 +546,7 @@ impl SasState { /// /// * `event` - The m.key.verification.mac event that was sent to us by /// the other side. - fn into_mac_received( + pub fn into_mac_received( self, event: &ToDeviceEvent, ) -> Result, SasState> { @@ -954,7 +576,7 @@ impl SasState { /// /// This needs to be done by the user, this will put us in the `Confirmed` /// state. - fn confirm(self) -> SasState { + pub fn confirm(self) -> SasState { SasState { inner: self.inner, verification_flow_id: self.verification_flow_id, @@ -974,7 +596,7 @@ impl SasState { /// /// * `event` - The m.key.verification.mac event that was sent to us by /// the other side. - fn into_done( + pub fn into_done( self, event: &ToDeviceEvent, ) -> Result, SasState> { @@ -1002,7 +624,7 @@ impl SasState { /// Get the content for the mac event. /// /// The content needs to be automatically sent to the other side. - fn as_content(&self) -> MacEventContent { + pub fn as_content(&self) -> MacEventContent { get_mac_content( &self.inner.lock().unwrap(), &self.ids, @@ -1016,7 +638,7 @@ impl SasState { /// /// This needs to be done by the user, this will put us in the `Done` /// state since the other side already confirmed and sent us a MAC event. - fn confirm(self) -> SasState { + pub fn confirm(self) -> SasState { SasState { inner: self.inner, verification_flow_id: self.verification_flow_id, @@ -1032,7 +654,7 @@ impl SasState { /// /// Returns a vector of tuples where the first element is the emoji and the /// second element the English description of the emoji. - fn get_emoji(&self) -> Vec<(&'static str, &'static str)> { + pub fn get_emoji(&self) -> Vec<(&'static str, &'static str)> { get_emoji( &self.inner.lock().unwrap(), &self.ids, @@ -1045,7 +667,7 @@ impl SasState { /// /// Returns a tuple containing three 4 digit integer numbers that represent /// the short auth string. - fn get_decimal(&self) -> (u32, u32, u32) { + pub fn get_decimal(&self) -> (u32, u32, u32) { get_decimal( &self.inner.lock().unwrap(), &self.ids, @@ -1060,7 +682,7 @@ impl SasState { /// /// The content needs to be automatically sent to the other side if it /// wasn't already sent. - fn as_content(&self) -> MacEventContent { + pub fn as_content(&self) -> MacEventContent { get_mac_content( &self.inner.lock().unwrap(), &self.ids, @@ -1069,12 +691,12 @@ impl SasState { } /// Get the list of verified devices. - fn verified_devices(&self) -> Arc>> { + pub fn verified_devices(&self) -> Arc>> { self.state.verified_devices.clone() } /// Get the list of verified master keys. - fn verified_master_keys(&self) -> Arc> { + pub fn verified_master_keys(&self) -> Arc> { self.state.verified_master_keys.clone() } } @@ -1108,7 +730,7 @@ impl Canceled { } impl SasState { - fn as_content(&self) -> AnyToDeviceEventContent { + pub fn as_content(&self) -> AnyToDeviceEventContent { AnyToDeviceEventContent::KeyVerificationCancel(CancelEventContent { transaction_id: self.verification_flow_id.to_string(), reason: self.state.reason.to_string(), @@ -1121,12 +743,11 @@ impl SasState { mod test { use std::convert::TryFrom; - use crate::verification::test::{get_content_from_request, wrap_any_to_device_content}; use crate::{Account, Device}; use matrix_sdk_common::events::{EventContent, ToDeviceEvent}; use matrix_sdk_common::identifiers::{DeviceId, UserId}; - use super::{Accepted, Created, Sas, SasState, Started}; + use super::{Accepted, Created, SasState, Started}; fn alice_id() -> UserId { UserId::try_from("@alice:example.org").unwrap() @@ -1233,60 +854,4 @@ mod test { assert!(bob.verified_devices().contains(&alice.device_id().into())); assert!(alice.verified_devices().contains(&bob.device_id().into())); } - - #[tokio::test] - async fn sas_wrapper_full() { - let alice = Account::new(&alice_id(), &alice_device_id()); - let alice_device = Device::from_account(&alice).await; - - let bob = Account::new(&bob_id(), &bob_device_id()); - let bob_device = Device::from_account(&bob).await; - - let (alice, content) = Sas::start(alice, bob_device); - let event = wrap_to_device_event(alice.user_id(), content); - - let bob = Sas::from_start_event(bob, alice_device, &event).unwrap(); - let mut event = wrap_any_to_device_content( - bob.user_id(), - get_content_from_request(&bob.accept().unwrap()), - ); - - let content = alice.receive_event(&mut event); - - assert!(!alice.can_be_presented()); - assert!(!bob.can_be_presented()); - - let mut event = wrap_any_to_device_content(alice.user_id(), content.unwrap()); - let mut event = - wrap_any_to_device_content(bob.user_id(), bob.receive_event(&mut event).unwrap()); - - assert!(bob.can_be_presented()); - - alice.receive_event(&mut event); - assert!(alice.can_be_presented()); - - assert_eq!(alice.emoji().unwrap(), bob.emoji().unwrap()); - assert_eq!(alice.decimals().unwrap(), bob.decimals().unwrap()); - - let mut event = wrap_any_to_device_content( - alice.user_id(), - get_content_from_request(&alice.confirm().unwrap()), - ); - bob.receive_event(&mut event); - - let mut event = wrap_any_to_device_content( - bob.user_id(), - get_content_from_request(&bob.confirm().unwrap()), - ); - alice.receive_event(&mut event); - - assert!(alice - .verified_devices() - .unwrap() - .contains(&bob.device_id().into())); - assert!(bob - .verified_devices() - .unwrap() - .contains(&alice.device_id().into())); - } }