mls_rs/
external_client.rs

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
5use crate::{
6    client::MlsError,
7    group::{
8        cipher_suite_provider, framing::MlsMessage, message_processor::validate_key_package,
9        ExportedTree,
10    },
11    time::MlsTime,
12    KeyPackage,
13};
14
15pub mod builder;
16mod config;
17mod group;
18
19pub(crate) use config::ExternalClientConfig;
20use mls_rs_core::{
21    crypto::{CryptoProvider, SignatureSecretKey},
22    identity::SigningIdentity,
23};
24
25use builder::{ExternalBaseConfig, ExternalClientBuilder};
26
27pub use group::{ExternalGroup, ExternalReceivedMessage, ExternalSnapshot};
28
29/// A client capable of observing a group's state without having
30/// private keys required to read content.
31///
32/// This structure is useful when an application is sending
33/// plaintext control messages in order to allow a central server
34/// to facilitate communication between users.
35///
36/// # Warning
37///
38/// This structure will only be able to observe groups that were
39/// created by clients that have the `encrypt_control_messages`
40/// option returned by [`MlsRules::encryption_options`](`crate::MlsRules::encryption_options`)
41/// set to `false`. Any control messages that are sent encrypted
42/// over the wire will break the ability of this client to track
43/// the resulting group state.
44pub struct ExternalClient<C> {
45    config: C,
46    signing_data: Option<(SignatureSecretKey, SigningIdentity)>,
47}
48
49impl ExternalClient<()> {
50    pub fn builder() -> ExternalClientBuilder<ExternalBaseConfig> {
51        ExternalClientBuilder::new()
52    }
53}
54
55impl<C> ExternalClient<C>
56where
57    C: ExternalClientConfig + Clone,
58{
59    pub(crate) fn new(
60        config: C,
61        signing_data: Option<(SignatureSecretKey, SigningIdentity)>,
62    ) -> Self {
63        Self {
64            config,
65            signing_data,
66        }
67    }
68
69    /// Begin observing a group based on a GroupInfo message created by
70    /// [Group::group_info_message](crate::group::Group::group_info_message)
71    ///
72    ///`tree_data` is required to be provided out of band if the client that
73    /// created GroupInfo message did not did not use the `ratchet_tree_extension`
74    /// according to [`MlsRules::commit_options`](crate::MlsRules::commit_options)
75    /// at the time the welcome message
76    /// was created. `tree_data` can be exported from a group using the
77    /// [export tree function](crate::group::Group::export_tree).
78    #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
79    pub async fn observe_group(
80        &self,
81        group_info: MlsMessage,
82        tree_data: Option<ExportedTree<'_>>,
83        maybe_time: Option<MlsTime>,
84    ) -> Result<ExternalGroup<C>, MlsError> {
85        ExternalGroup::join(
86            self.config.clone(),
87            self.signing_data.clone(),
88            group_info,
89            tree_data,
90            maybe_time,
91        )
92        .await
93    }
94
95    /// Load an existing observed group by loading a snapshot that was
96    /// generated by
97    /// [ExternalGroup::snapshot](self::ExternalGroup::snapshot).
98    #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
99    pub async fn load_group(
100        &self,
101        snapshot: ExternalSnapshot,
102    ) -> Result<ExternalGroup<C>, MlsError> {
103        #[cfg(feature = "tree_index")]
104        let identity_provider = self.config.identity_provider();
105
106        let cipher_suite_provider = cipher_suite_provider(
107            self.config.crypto_provider(),
108            snapshot.state.context.cipher_suite,
109        )?;
110
111        Ok(ExternalGroup {
112            config: self.config.clone(),
113            signing_data: self.signing_data.clone(),
114            state: snapshot
115                .state
116                .import(
117                    #[cfg(feature = "tree_index")]
118                    &identity_provider,
119                )
120                .await?,
121            cipher_suite_provider,
122        })
123    }
124
125    /// Load an existing observed group by loading a snapshot that was
126    /// generated by
127    /// [ExternalGroup::snapshot](self::ExternalGroup::snapshot). The tree
128    /// is taken from `tree_data` instead of the stored state.
129    #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
130    pub async fn load_group_with_ratchet_tree(
131        &self,
132        mut snapshot: ExternalSnapshot,
133        tree_data: ExportedTree<'_>,
134    ) -> Result<ExternalGroup<C>, MlsError> {
135        snapshot.state.public_tree.nodes = tree_data.0.into_owned();
136
137        self.load_group(snapshot).await
138    }
139
140    #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
141    pub async fn validate_key_package(
142        &self,
143        key_package: MlsMessage,
144        timestamp: Option<MlsTime>,
145    ) -> Result<KeyPackage, MlsError> {
146        let version = key_package.version;
147
148        let key_package = key_package
149            .into_key_package()
150            .ok_or(MlsError::UnexpectedMessageType)?;
151
152        let cs = self
153            .config
154            .crypto_provider()
155            .cipher_suite_provider(key_package.cipher_suite)
156            .ok_or(MlsError::UnsupportedCipherSuite(key_package.cipher_suite))?;
157
158        let id = self.config.identity_provider();
159
160        validate_key_package(&key_package, version, &cs, &id, timestamp).await?;
161
162        Ok(key_package)
163    }
164
165    /// The [IdentityProvider](crate::IdentityProvider) that this client was configured to use.
166    pub fn identity_provider(&self) -> <C as ExternalClientConfig>::IdentityProvider {
167        self.config.identity_provider()
168    }
169}
170
171#[cfg(test)]
172pub(crate) mod tests_utils {
173    use crate::{
174        client::test_utils::{TEST_CIPHER_SUITE, TEST_PROTOCOL_VERSION},
175        key_package::test_utils::test_key_package_message,
176    };
177
178    pub use super::builder::test_utils::*;
179
180    #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
181    async fn external_client_can_validate_key_package() {
182        let kp = test_key_package_message(TEST_PROTOCOL_VERSION, TEST_CIPHER_SUITE, "john").await;
183        let server = TestExternalClientBuilder::new_for_test().build();
184        let validated_kp = server.validate_key_package(kp.clone(), None).await.unwrap();
185
186        assert_eq!(kp.into_key_package().unwrap(), validated_kp);
187    }
188}