1 // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 // Copyright by contributors to this project. 3 // SPDX-License-Identifier: (Apache-2.0 OR MIT) 4 5 use crate::{ 6 client::MlsError, 7 group::{framing::MlsMessage, message_processor::validate_key_package, ExportedTree}, 8 KeyPackage, 9 }; 10 11 pub mod builder; 12 mod config; 13 mod group; 14 15 pub(crate) use config::ExternalClientConfig; 16 use mls_rs_core::{ 17 crypto::{CryptoProvider, SignatureSecretKey}, 18 identity::SigningIdentity, 19 }; 20 21 use builder::{ExternalBaseConfig, ExternalClientBuilder}; 22 23 pub use group::{ExternalGroup, ExternalReceivedMessage, ExternalSnapshot}; 24 25 /// A client capable of observing a group's state without having 26 /// private keys required to read content. 27 /// 28 /// This structure is useful when an application is sending 29 /// plaintext control messages in order to allow a central server 30 /// to facilitate communication between users. 31 /// 32 /// # Warning 33 /// 34 /// This structure will only be able to observe groups that were 35 /// created by clients that have the `encrypt_control_messages` 36 /// option returned by [`MlsRules::encryption_options`](`crate::MlsRules::encryption_options`) 37 /// set to `false`. Any control messages that are sent encrypted 38 /// over the wire will break the ability of this client to track 39 /// the resulting group state. 40 pub struct ExternalClient<C> { 41 config: C, 42 signing_data: Option<(SignatureSecretKey, SigningIdentity)>, 43 } 44 45 impl ExternalClient<()> { builder() -> ExternalClientBuilder<ExternalBaseConfig>46 pub fn builder() -> ExternalClientBuilder<ExternalBaseConfig> { 47 ExternalClientBuilder::new() 48 } 49 } 50 51 impl<C> ExternalClient<C> 52 where 53 C: ExternalClientConfig + Clone, 54 { new( config: C, signing_data: Option<(SignatureSecretKey, SigningIdentity)>, ) -> Self55 pub(crate) fn new( 56 config: C, 57 signing_data: Option<(SignatureSecretKey, SigningIdentity)>, 58 ) -> Self { 59 Self { 60 config, 61 signing_data, 62 } 63 } 64 65 /// Begin observing a group based on a GroupInfo message created by 66 /// [Group::group_info_message](crate::group::Group::group_info_message) 67 /// 68 ///`tree_data` is required to be provided out of band if the client that 69 /// created GroupInfo message did not did not use the `ratchet_tree_extension` 70 /// according to [`MlsRules::commit_options`](crate::MlsRules::commit_options) 71 /// at the time the welcome message 72 /// was created. `tree_data` can be exported from a group using the 73 /// [export tree function](crate::group::Group::export_tree). 74 #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)] observe_group( &self, group_info: MlsMessage, tree_data: Option<ExportedTree<'_>>, ) -> Result<ExternalGroup<C>, MlsError>75 pub async fn observe_group( 76 &self, 77 group_info: MlsMessage, 78 tree_data: Option<ExportedTree<'_>>, 79 ) -> Result<ExternalGroup<C>, MlsError> { 80 ExternalGroup::join( 81 self.config.clone(), 82 self.signing_data.clone(), 83 group_info, 84 tree_data, 85 ) 86 .await 87 } 88 89 /// Load an existing observed group by loading a snapshot that was 90 /// generated by 91 /// [ExternalGroup::snapshot](self::ExternalGroup::snapshot). 92 #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)] load_group( &self, snapshot: ExternalSnapshot, ) -> Result<ExternalGroup<C>, MlsError>93 pub async fn load_group( 94 &self, 95 snapshot: ExternalSnapshot, 96 ) -> Result<ExternalGroup<C>, MlsError> { 97 ExternalGroup::from_snapshot(self.config.clone(), snapshot).await 98 } 99 100 #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)] validate_key_package( &self, key_package: MlsMessage, ) -> Result<KeyPackage, MlsError>101 pub async fn validate_key_package( 102 &self, 103 key_package: MlsMessage, 104 ) -> Result<KeyPackage, MlsError> { 105 let version = key_package.version; 106 107 let key_package = key_package 108 .into_key_package() 109 .ok_or(MlsError::UnexpectedMessageType)?; 110 111 let cs = self 112 .config 113 .crypto_provider() 114 .cipher_suite_provider(key_package.cipher_suite) 115 .ok_or(MlsError::UnsupportedCipherSuite(key_package.cipher_suite))?; 116 117 let id = self.config.identity_provider(); 118 119 validate_key_package(&key_package, version, &cs, &id).await?; 120 121 Ok(key_package) 122 } 123 } 124 125 #[cfg(test)] 126 pub(crate) mod tests_utils { 127 use crate::{ 128 client::test_utils::{TEST_CIPHER_SUITE, TEST_PROTOCOL_VERSION}, 129 key_package::test_utils::test_key_package_message, 130 }; 131 132 pub use super::builder::test_utils::*; 133 134 #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))] external_client_can_validate_key_package()135 async fn external_client_can_validate_key_package() { 136 let kp = test_key_package_message(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE, "john").await; 137 let server = TestExternalClientBuilder::new_for_test().build(); 138 let validated_kp = server.validate_key_package(kp.clone()).await.unwrap(); 139 140 assert_eq!(kp.into_key_package().unwrap(), validated_kp); 141 } 142 } 143