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