// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // Copyright by contributors to this project. // SPDX-License-Identifier: (Apache-2.0 OR MIT) use crate::{ client::MlsError, group::{framing::MlsMessage, message_processor::validate_key_package, ExportedTree}, KeyPackage, }; pub mod builder; mod config; mod group; pub(crate) use config::ExternalClientConfig; use mls_rs_core::{ crypto::{CryptoProvider, SignatureSecretKey}, identity::SigningIdentity, }; use builder::{ExternalBaseConfig, ExternalClientBuilder}; pub use group::{ExternalGroup, ExternalReceivedMessage, ExternalSnapshot}; /// A client capable of observing a group's state without having /// private keys required to read content. /// /// This structure is useful when an application is sending /// plaintext control messages in order to allow a central server /// to facilitate communication between users. /// /// # Warning /// /// This structure will only be able to observe groups that were /// created by clients that have the `encrypt_control_messages` /// option returned by [`MlsRules::encryption_options`](`crate::MlsRules::encryption_options`) /// set to `false`. Any control messages that are sent encrypted /// over the wire will break the ability of this client to track /// the resulting group state. pub struct ExternalClient { config: C, signing_data: Option<(SignatureSecretKey, SigningIdentity)>, } impl ExternalClient<()> { pub fn builder() -> ExternalClientBuilder { ExternalClientBuilder::new() } } impl ExternalClient where C: ExternalClientConfig + Clone, { pub(crate) fn new( config: C, signing_data: Option<(SignatureSecretKey, SigningIdentity)>, ) -> Self { Self { config, signing_data, } } /// Begin observing a group based on a GroupInfo message created by /// [Group::group_info_message](crate::group::Group::group_info_message) /// ///`tree_data` is required to be provided out of band if the client that /// created GroupInfo message did not did not use the `ratchet_tree_extension` /// according to [`MlsRules::commit_options`](crate::MlsRules::commit_options) /// at the time the welcome message /// was created. `tree_data` can be exported from a group using the /// [export tree function](crate::group::Group::export_tree). #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)] pub async fn observe_group( &self, group_info: MlsMessage, tree_data: Option>, ) -> Result, MlsError> { ExternalGroup::join( self.config.clone(), self.signing_data.clone(), group_info, tree_data, ) .await } /// Load an existing observed group by loading a snapshot that was /// generated by /// [ExternalGroup::snapshot](self::ExternalGroup::snapshot). #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)] pub async fn load_group( &self, snapshot: ExternalSnapshot, ) -> Result, MlsError> { ExternalGroup::from_snapshot(self.config.clone(), snapshot).await } #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)] pub async fn validate_key_package( &self, key_package: MlsMessage, ) -> Result { let version = key_package.version; let key_package = key_package .into_key_package() .ok_or(MlsError::UnexpectedMessageType)?; let cs = self .config .crypto_provider() .cipher_suite_provider(key_package.cipher_suite) .ok_or(MlsError::UnsupportedCipherSuite(key_package.cipher_suite))?; let id = self.config.identity_provider(); validate_key_package(&key_package, version, &cs, &id).await?; Ok(key_package) } } #[cfg(test)] pub(crate) mod tests_utils { use crate::{ client::test_utils::{TEST_CIPHER_SUITE, TEST_PROTOCOL_VERSION}, key_package::test_utils::test_key_package_message, }; pub use super::builder::test_utils::*; #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))] async fn external_client_can_validate_key_package() { let kp = test_key_package_message(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE, "john").await; let server = TestExternalClientBuilder::new_for_test().build(); let validated_kp = server.validate_key_package(kp.clone()).await.unwrap(); assert_eq!(kp.into_key_package().unwrap(), validated_kp); } }