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