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 use alloc::{vec, vec::Vec};
5 
6 use mls_rs_codec::{MlsDecode, MlsEncode, MlsSize};
7 use mls_rs_core::crypto::HpkeSecretKey;
8 
9 use crate::{client::MlsError, crypto::CipherSuiteProvider};
10 
11 use super::{
12     math::leaf_lca_level,
13     node::LeafIndex,
14     path_secret::{PathSecret, PathSecretGenerator},
15     TreeKemPublic,
16 };
17 
18 #[derive(Clone, Debug, MlsEncode, MlsDecode, MlsSize, Eq, PartialEq)]
19 #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
20 #[non_exhaustive]
21 pub struct TreeKemPrivate {
22     pub self_index: LeafIndex,
23     pub secret_keys: Vec<Option<HpkeSecretKey>>,
24 }
25 
26 impl TreeKemPrivate {
new_self_leaf(self_index: LeafIndex, leaf_secret: HpkeSecretKey) -> Self27     pub fn new_self_leaf(self_index: LeafIndex, leaf_secret: HpkeSecretKey) -> Self {
28         TreeKemPrivate {
29             self_index,
30             secret_keys: vec![Some(leaf_secret)],
31         }
32     }
33 
new_for_external() -> Self34     pub fn new_for_external() -> Self {
35         TreeKemPrivate {
36             self_index: LeafIndex(0),
37             secret_keys: Default::default(),
38         }
39     }
40 
41     #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
update_secrets<P: CipherSuiteProvider>( &mut self, cipher_suite_provider: &P, signer_index: LeafIndex, path_secret: PathSecret, public_tree: &TreeKemPublic, ) -> Result<(), MlsError>42     pub async fn update_secrets<P: CipherSuiteProvider>(
43         &mut self,
44         cipher_suite_provider: &P,
45         signer_index: LeafIndex,
46         path_secret: PathSecret,
47         public_tree: &TreeKemPublic,
48     ) -> Result<(), MlsError> {
49         // Identify the lowest common
50         // ancestor of the leaves at index and at GroupInfo.signer_index. Set the private key
51         // for this node to the private key derived from the path_secret.
52         let lca_index = leaf_lca_level(self.self_index.into(), signer_index.into()) as usize - 2;
53 
54         // For each parent of the common ancestor, up to the root of the tree, derive a new
55         // path secret and set the private key for the node to the private key derived from the
56         // path secret. The private key MUST be the private key that corresponds to the public
57         // key in the node.
58 
59         let mut node_secret_gen =
60             PathSecretGenerator::starting_with(cipher_suite_provider, path_secret);
61 
62         let path = public_tree.nodes.direct_copath(self.self_index);
63         let filtered = &public_tree.nodes.filtered(self.self_index)?;
64         self.secret_keys.resize(path.len() + 1, None);
65 
66         for (i, (n, f)) in path.iter().zip(filtered).enumerate().skip(lca_index) {
67             if *f {
68                 continue;
69             }
70 
71             let secret = node_secret_gen.next_secret().await?;
72 
73             let expected_pub_key = public_tree
74                 .nodes
75                 .borrow_node(n.path)?
76                 .as_ref()
77                 .map(|n| n.public_key())
78                 .ok_or(MlsError::PubKeyMismatch)?;
79 
80             let (secret_key, public_key) = secret.to_hpke_key_pair(cipher_suite_provider).await?;
81 
82             if expected_pub_key != &public_key {
83                 return Err(MlsError::PubKeyMismatch);
84             }
85 
86             // It's ok to use index directly because of the resize above
87             self.secret_keys[i + 1] = Some(secret_key);
88         }
89 
90         Ok(())
91     }
92 
93     #[cfg(feature = "by_ref_proposal")]
update_leaf(&mut self, new_leaf: HpkeSecretKey)94     pub fn update_leaf(&mut self, new_leaf: HpkeSecretKey) {
95         self.secret_keys = vec![None; self.secret_keys.len()];
96         self.secret_keys[0] = Some(new_leaf);
97     }
98 }
99 
100 #[cfg(test)]
101 impl TreeKemPrivate {
new(self_index: LeafIndex) -> Self102     pub fn new(self_index: LeafIndex) -> Self {
103         TreeKemPrivate {
104             self_index,
105             secret_keys: Default::default(),
106         }
107     }
108 }
109 
110 #[cfg(test)]
111 mod tests {
112     use assert_matches::assert_matches;
113 
114     use crate::{
115         cipher_suite::CipherSuite,
116         client::test_utils::TEST_CIPHER_SUITE,
117         crypto::test_utils::test_cipher_suite_provider,
118         group::test_utils::{get_test_group_context, random_bytes},
119         identity::basic::BasicIdentityProvider,
120         tree_kem::{
121             kem::TreeKem,
122             leaf_node::test_utils::{
123                 default_properties, get_basic_test_node, get_basic_test_node_sig_key,
124             },
125             math::TreeIndex,
126             node::LeafIndex,
127         },
128     };
129 
130     use super::*;
131 
132     #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
random_hpke_secret_key() -> HpkeSecretKey133     async fn random_hpke_secret_key() -> HpkeSecretKey {
134         let (secret, _) = test_cipher_suite_provider(TEST_CIPHER_SUITE)
135             .kem_derive(&random_bytes(32))
136             .await
137             .unwrap();
138 
139         secret
140     }
141 
142     #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
test_create_self_leaf()143     async fn test_create_self_leaf() {
144         let secret = random_hpke_secret_key().await;
145 
146         let self_index = LeafIndex(42);
147 
148         let private_key = TreeKemPrivate::new_self_leaf(self_index, secret.clone());
149 
150         assert_eq!(private_key.self_index, self_index);
151         assert_eq!(private_key.secret_keys.len(), 1);
152         assert_eq!(private_key.secret_keys[0].as_ref().unwrap(), &secret)
153     }
154 
155     // Create a ratchet tree for Alice, Bob and Charlie. Alice generates an update path for
156     // Charlie. Return (Public Tree, Charlie's private key, update path, path secret)
157     // The ratchet tree returned has leaf indexes as [alice, bob, charlie]
158     #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
update_secrets_setup( cipher_suite: CipherSuite, ) -> (TreeKemPublic, TreeKemPrivate, TreeKemPrivate, PathSecret)159     async fn update_secrets_setup(
160         cipher_suite: CipherSuite,
161     ) -> (TreeKemPublic, TreeKemPrivate, TreeKemPrivate, PathSecret) {
162         let cipher_suite_provider = test_cipher_suite_provider(cipher_suite);
163 
164         let (alice_leaf, alice_hpke_secret, alice_signing) =
165             get_basic_test_node_sig_key(cipher_suite, "alice").await;
166 
167         let bob_leaf = get_basic_test_node(cipher_suite, "bob").await;
168 
169         let (charlie_leaf, charlie_hpke_secret, _charlie_signing) =
170             get_basic_test_node_sig_key(cipher_suite, "charlie").await;
171 
172         // Create a new public tree with Alice
173         let (mut public_tree, mut alice_private) = TreeKemPublic::derive(
174             alice_leaf,
175             alice_hpke_secret,
176             &BasicIdentityProvider,
177             &Default::default(),
178         )
179         .await
180         .unwrap();
181 
182         // Add bob and charlie to the tree
183         public_tree
184             .add_leaves(
185                 vec![bob_leaf, charlie_leaf],
186                 &BasicIdentityProvider,
187                 &cipher_suite_provider,
188             )
189             .await
190             .unwrap();
191 
192         // Alice's secret key is longer now
193         alice_private.secret_keys.resize(3, None);
194 
195         // Generate an update path for Alice
196         let encap_gen = TreeKem::new(&mut public_tree, &mut alice_private)
197             .encap(
198                 &mut get_test_group_context(42, cipher_suite).await,
199                 &[],
200                 &alice_signing,
201                 default_properties(),
202                 None,
203                 &cipher_suite_provider,
204                 #[cfg(test)]
205                 &Default::default(),
206             )
207             .await
208             .unwrap();
209 
210         // Get a path secret from Alice for Charlie
211         let path_secret = encap_gen.path_secrets[1].clone().unwrap();
212 
213         // Private key for Charlie
214         let charlie_private = TreeKemPrivate::new_self_leaf(LeafIndex(2), charlie_hpke_secret);
215 
216         (public_tree, charlie_private, alice_private, path_secret)
217     }
218 
219     #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
test_update_secrets()220     async fn test_update_secrets() {
221         let cipher_suite = TEST_CIPHER_SUITE;
222 
223         let (public_tree, mut charlie_private, alice_private, path_secret) =
224             update_secrets_setup(cipher_suite).await;
225 
226         let existing_private = charlie_private.secret_keys.first().cloned().unwrap();
227 
228         // Add the secrets for Charlie to his private key
229         charlie_private
230             .update_secrets(
231                 &test_cipher_suite_provider(cipher_suite),
232                 LeafIndex(0),
233                 path_secret,
234                 &public_tree,
235             )
236             .await
237             .unwrap();
238 
239         // Make sure that Charlie's private key didn't lose keys
240         assert_eq!(charlie_private.secret_keys.len(), 3);
241 
242         // Check that the intersection of the secret keys of Alice and Charlie matches.
243         // The intersection contains only the root.
244         assert_eq!(alice_private.secret_keys[2], charlie_private.secret_keys[2]);
245 
246         assert_eq!(
247             charlie_private.secret_keys[0].as_ref(),
248             existing_private.as_ref()
249         );
250     }
251 
252     #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
test_update_secrets_key_mismatch()253     async fn test_update_secrets_key_mismatch() {
254         let cipher_suite = TEST_CIPHER_SUITE;
255 
256         let (mut public_tree, mut charlie_private, _, path_secret) =
257             update_secrets_setup(cipher_suite).await;
258 
259         // Sabotage the public tree
260         public_tree
261             .nodes
262             .borrow_as_parent_mut(public_tree.total_leaf_count().root())
263             .unwrap()
264             .public_key = random_bytes(32).into();
265 
266         // Add the secrets for Charlie to his private key
267         let res = charlie_private
268             .update_secrets(
269                 &test_cipher_suite_provider(cipher_suite),
270                 LeafIndex(0),
271                 path_secret,
272                 &public_tree,
273             )
274             .await;
275 
276         assert_matches!(res, Err(MlsError::PubKeyMismatch));
277     }
278 
279     #[cfg(feature = "by_ref_proposal")]
280     #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
setup_direct_path(self_index: LeafIndex, leaf_count: u32) -> TreeKemPrivate281     async fn setup_direct_path(self_index: LeafIndex, leaf_count: u32) -> TreeKemPrivate {
282         let secret = random_hpke_secret_key().await;
283 
284         let mut private_key = TreeKemPrivate::new_self_leaf(self_index, secret.clone());
285 
286         private_key.secret_keys = (0..0.direct_copath(&leaf_count).len() + 1)
287             .map(|_| Some(secret.clone()))
288             .collect();
289 
290         private_key
291     }
292 
293     #[cfg(feature = "by_ref_proposal")]
294     #[maybe_async::test(not(mls_build_async), async(mls_build_async, crate::futures_test))]
test_update_leaf()295     async fn test_update_leaf() {
296         let self_leaf = LeafIndex(42);
297         let mut private_key = setup_direct_path(self_leaf, 128).await;
298 
299         let new_secret = random_hpke_secret_key().await;
300 
301         private_key.update_leaf(new_secret.clone());
302 
303         // The update operation should have removed all the other keys in our direct path we
304         // previously added
305         assert!(private_key.secret_keys.iter().skip(1).all(|n| n.is_none()));
306 
307         // The secret key for our leaf should have been updated accordingly
308         assert_eq!(private_key.secret_keys.first().unwrap(), &Some(new_secret));
309     }
310 }
311