From 8ff8ea1342dbdffa314862a0a243adc159d448b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Thu, 23 Jul 2020 17:25:57 +0200 Subject: [PATCH] crypto: Add docs for the SAS structs and methods. --- matrix_sdk_crypto/src/verification/mod.rs | 158 ++++++++++++++-- matrix_sdk_crypto/src/verification/sas.rs | 218 ++++++++++++++++++---- 2 files changed, 322 insertions(+), 54 deletions(-) diff --git a/matrix_sdk_crypto/src/verification/mod.rs b/matrix_sdk_crypto/src/verification/mod.rs index ed0c16ed..8ad1561a 100644 --- a/matrix_sdk_crypto/src/verification/mod.rs +++ b/matrix_sdk_crypto/src/verification/mod.rs @@ -31,6 +31,16 @@ struct SasIds { 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"), @@ -97,10 +107,18 @@ fn emoji_from_index(index: u8) -> (&'static str, &'static str) { 61 => ("🎧", "Headphones"), 62 => ("📁", "Folder"), 63 => ("📌", "Pin"), - _ => panic!("Trying to fetch an SAS emoji outside the allowed range"), + _ => 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}\ @@ -113,12 +131,28 @@ fn extra_mac_info_receive(ids: &SasIds, flow_id: &str) -> String { ) } +/// 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); @@ -145,8 +179,8 @@ fn receive_mac_event( } 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) { @@ -156,6 +190,8 @@ fn receive_mac_event( .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 @@ -164,6 +200,14 @@ fn receive_mac_event( (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}\ @@ -176,6 +220,21 @@ fn extra_mac_info_send(ids: &SasIds, flow_id: &str) -> String { ) } +/// 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(); @@ -204,30 +263,63 @@ fn get_mac_content(sas: &OlmSas, ids: &SasIds, flow_id: &str) -> MacEventContent } } +/// 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 { - if we_started { - format!( - "MATRIX_KEY_VERIFICATION_SAS{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, + 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 { - format!( - "MATRIX_KEY_VERIFICATION_SAS{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, + ( + 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, @@ -241,6 +333,8 @@ fn get_emoji( .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; @@ -248,6 +342,8 @@ fn get_emoji( 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, @@ -258,9 +354,29 @@ fn get_emoji( ((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) @@ -269,6 +385,8 @@ fn get_decimal(sas: &OlmSas, ids: &SasIds, flow_id: &str, we_started: bool) -> ( .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; diff --git a/matrix_sdk_crypto/src/verification/sas.rs b/matrix_sdk_crypto/src/verification/sas.rs index a4f61c3f..4cc32b5e 100644 --- a/matrix_sdk_crypto/src/verification/sas.rs +++ b/matrix_sdk_crypto/src/verification/sas.rs @@ -33,6 +33,8 @@ use matrix_sdk_common::uuid::Uuid; use super::{get_decimal, get_emoji, get_mac_content, receive_mac_event, SasIds}; use crate::{Account, Device}; +/// Struct containing the protocols that were agreed to be used for the SAS +/// flow. struct AcceptedProtocols { method: VerificationMethod, key_agreement_protocol: KeyAgreementProtocol, @@ -53,24 +55,99 @@ impl From for AcceptedProtocols { } } +// TODO each of our state transitions can fail and return a canceled state. We +// need to check the senders at each transition, the commitment, the +// verification flow id (transaction id). + +/// A type level state machine modeling the Sas flow. +/// +/// This is the generic struc holding common data between the different states +/// and the specific state. struct Sas { + /// The Olm SAS struct. inner: OlmSas, + /// Struct holding the identities that are doing the SAS dance. ids: SasIds, + /// The unique identifier of this SAS flow. + /// + /// This will be the transaction id for to-device events and the relates_to + /// field for in-room events. verification_flow_id: String, + /// The SAS state we're in. state: S, } +/// The initial SAS state. +struct Created { + protocol_definitions: MSasV1ContentOptions, +} + +/// The initial SAS state if the other side started the SAS verification. +struct Started { + protocol_definitions: MSasV1Content, +} + +/// The SAS state we're going to be in after the other side accepted our +/// verification start event. +struct Accepted { + accepted_protocols: AcceptedProtocols, + commitment: String, +} + +/// The SAS state we're going to be in after we received the public key of the +/// other participant. +/// +/// From now on we can show the short auth string to the user. +struct KeyReceived { + we_started: bool, + accepted_protocols: AcceptedProtocols, +} + +/// The SAS state we're going to be in after the user has confirmed that the +/// short auth string matches. We still need to receive a MAC event from the +/// other side. +struct Confirmed { + accepted_protocols: AcceptedProtocols, +} + +/// The SAS state we're going to be in after we receive a MAC event from the +/// other side. Our own user still needs to confirm that the short auth string +/// matches. +struct MacReceived { + we_started: bool, + verified_devices: Vec>, + verified_master_keys: Vec, +} + +/// The SAS state indicating that the verification finished successfully. +/// +/// We can now mark the device in our verified devices lits as verified and sign +/// the master keys in the verified devices list. +struct Done { + verified_devices: Vec>, + verified_master_keys: Vec, +} + impl Sas { + /// Get our own user id. pub fn user_id(&self) -> &UserId { &self.ids.account.user_id() } + /// Get our own device id. pub fn device_id(&self) -> &DeviceId { &self.ids.account.device_id() } } impl Sas { + /// Create a new SAS verification flow. + /// + /// # Arguments + /// + /// * `account` - Our own account. + /// + /// * `other_device` - The other device which we are going to verify. fn new(account: Account, other_device: Device) -> Sas { let verification_flow_id = Uuid::new_v4().to_string(); let from_device: Box = account.device_id().into(); @@ -99,6 +176,9 @@ impl Sas { } } + /// Get the content for the start event. + /// + /// The content needs to be sent to the other device. fn get_start_event(&self) -> StartEventContent { StartEventContent::MSasV1( MSasV1Content::new(self.state.protocol_definitions.clone()) @@ -106,9 +186,18 @@ impl Sas { ) } + /// Receive a m.key.verification.accept event, changing the state into + /// an Accepted one. + /// + /// # Arguments + /// + /// * `event` - The m.key.verification.accept event that was sent to us by + /// the other side. fn into_accepted(self, event: &ToDeviceEvent) -> Sas { let content = &event.content; + // TODO check that we support the agreed upon protocols, cancel if not. + Sas { inner: self.inner, ids: self.ids, @@ -121,20 +210,27 @@ impl Sas { } } -struct Created { - protocol_definitions: MSasV1ContentOptions, -} - -struct Started { - protocol_definitions: MSasV1Content, -} - impl Sas { + /// Create a new SAS verification flow from a m.key.verification.start + /// event. + /// + /// This will put us in the `started` state. + /// + /// # 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. fn from_start_event( account: Account, other_device: Device, event: &ToDeviceEvent, ) -> Sas { + // TODO check if we support the suggested protocols and cancel if we + // don't let content = if let StartEventContent::MSasV1(content) = &event.content { content } else { @@ -157,10 +253,18 @@ impl Sas { } } + /// Get the content for the accept event. + /// + /// The content needs to be sent to the other device. + /// + /// 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 get_accept_content(&self) -> AcceptEventContent { AcceptEventContent { method: VerificationMethod::MSasV1, transaction_id: self.verification_flow_id.to_string(), + // TODO calculate the commitment. commitment: "".to_owned(), hash: HashAlgorithm::Sha256, key_agreement_protocol: KeyAgreementProtocol::Curve25519HkdfSha256, @@ -173,6 +277,14 @@ impl Sas { } } + /// Receive a m.key.verification.key event, changing the state into + /// a `KeyReceived` one + /// + /// # Arguments + /// + /// * `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(mut self, event: &mut ToDeviceEvent) -> Sas { let accepted_protocols: AcceptedProtocols = self.get_accept_content().into(); self.inner @@ -191,13 +303,17 @@ impl Sas { } } -struct Accepted { - accepted_protocols: AcceptedProtocols, - commitment: String, -} - impl Sas { + /// Receive a m.key.verification.key event, changing the state into + /// a `KeyReceived` one + /// + /// # Arguments + /// + /// * `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(mut self, event: &mut ToDeviceEvent) -> Sas { + // TODO check the commitment here since we started the SAS dance. self.inner .set_their_public_key(&mem::take(&mut event.content.key)) .expect("Can't set public key"); @@ -213,6 +329,9 @@ impl Sas { } } + /// Get the content for the key event. + /// + /// The content needs to be automatically sent to the other side. fn get_key_content(&self) -> KeyEventContent { KeyEventContent { transaction_id: self.verification_flow_id.to_string(), @@ -221,12 +340,11 @@ impl Sas { } } -struct KeyReceived { - we_started: bool, - accepted_protocols: AcceptedProtocols, -} - impl Sas { + /// Get the content for the key event. + /// + /// The content needs to be automatically sent to the other side if and only + /// if we_started is false. fn get_key_content(&self) -> KeyEventContent { KeyEventContent { transaction_id: self.verification_flow_id.to_string(), @@ -234,6 +352,10 @@ impl Sas { } } + /// 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. fn get_emoji(&self) -> Vec<(&'static str, &'static str)> { get_emoji( &self.inner, @@ -243,6 +365,10 @@ impl Sas { ) } + /// Get the decimal version of the short authentication string. + /// + /// Returns a tuple containing three 4 digit integer numbers that represent + /// the short auth string. fn get_decimal(&self) -> (u32, u32, u32) { get_decimal( &self.inner, @@ -252,6 +378,13 @@ impl Sas { ) } + /// Receive a m.key.verification.mac event, changing the state into + /// a `MacReceived` one + /// + /// # Arguments + /// + /// * `event` - The m.key.verification.mac event that was sent to us by + /// the other side. fn into_mac_received(self, event: &ToDeviceEvent) -> Sas { let (devices, master_keys) = receive_mac_event(&self.inner, &self.ids, &self.verification_flow_id, event); @@ -267,6 +400,10 @@ impl Sas { } } + /// Confirm that the short auth string matches. + /// + /// This needs to be done by the user, this will put us in the `Confirmed` + /// state. fn confirm(self) -> Sas { Sas { inner: self.inner, @@ -279,11 +416,14 @@ impl Sas { } } -struct Confirmed { - accepted_protocols: AcceptedProtocols, -} - impl Sas { + /// Receive a m.key.verification.mac event, changing the state into + /// a `Done` one + /// + /// # Arguments + /// + /// * `event` - The m.key.verification.mac event that was sent to us by + /// the other side. fn into_done(self, event: &ToDeviceEvent) -> Sas { let (devices, master_keys) = receive_mac_event(&self.inner, &self.ids, &self.verification_flow_id, event); @@ -300,18 +440,19 @@ impl Sas { } } + /// Get the content for the mac event. + /// + /// The content needs to be automatically sent to the other side. fn get_mac_event_content(&self) -> MacEventContent { get_mac_content(&self.inner, &self.ids, &self.verification_flow_id) } } -struct MacReceived { - we_started: bool, - verified_devices: Vec>, - verified_master_keys: Vec, -} - impl Sas { + /// Confirm that the short auth string matches. + /// + /// 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) -> Sas { Sas { inner: self.inner, @@ -324,6 +465,10 @@ impl Sas { } } + /// 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. fn get_emoji(&self) -> Vec<(&'static str, &'static str)> { get_emoji( &self.inner, @@ -333,6 +478,10 @@ impl Sas { ) } + /// Get the decimal version of the short authentication string. + /// + /// Returns a tuple containing three 4 digit integer numbers that represent + /// the short auth string. fn get_decimal(&self) -> (u32, u32, u32) { get_decimal( &self.inner, @@ -343,20 +492,21 @@ impl Sas { } } -struct Done { - verified_devices: Vec>, - verified_master_keys: Vec, -} - impl Sas { + /// Get the content for the mac event. + /// + /// The content needs to be automatically sent to the other side if it + /// wasn't already sent. fn get_mac_event_content(&self) -> MacEventContent { get_mac_content(&self.inner, &self.ids, &self.verification_flow_id) } + /// Get the list of verified devices. fn verified_devices(&self) -> &Vec> { &self.state.verified_devices } + /// Get the list of verified master keys. fn verified_master_keys(&self) -> &[String] { &self.state.verified_master_keys } @@ -446,7 +596,7 @@ mod test { } #[tokio::test] - async fn sas_mac() { + async fn sas_full() { let (alice, bob) = get_sas_pair().await; let event = wrap_to_device_event(bob.user_id(), bob.get_accept_content());