1use openmls_traits::{
2 crypto::OpenMlsCrypto,
3 signatures::Signer,
4 storage::{self, CURRENT_VERSION},
5 types::Ciphersuite,
6 OpenMlsProvider,
7};
8use thiserror::Error;
9use tls_codec::{Deserialize, Serialize, SerializeBytes, TlsSerialize, TlsSize, VLBytes};
10
11use crate::{
12 ciphersuite::{hash_ref::HashReference, hpke, AeadKey, AeadNonce, Mac, Secret},
13 error::LibraryError,
14 group::{CreateCommitError, WelcomeError, WelcomeKeyMaterial},
15 key_packages::{KeyPackage, KeyPackageBundle},
16 messages::{
17 group_info::{GroupInfo, GroupInfoTBS, VerifiableGroupInfo},
18 ConfirmationTag, GroupSecrets, GroupSecretsError, PathSecret, Welcome,
19 },
20 schedule::{
21 errors::{KeyScheduleError, PskError},
22 psk::{load_psks, store::ResumptionPskStore, PreSharedKeyId, PskSecret},
23 JoinerSecret, KeySchedule, Psk, WelcomeSecret,
24 },
25 storage::StorageProvider,
26 test_utils::frankenstein::{
27 group_info::FrankenGroupInfo, key_package, FrankenEncryptedGroupSecrets,
28 FrankenGroupSecrets, FrankenKeyPackage, FrankenPathSecret, FrankenWelcome,
29 },
30};
31
32use super::FrankenAuthenticatedContentTbm;
33
34pub fn compute_membership_tag(
36 crypto: &impl OpenMlsCrypto,
37 ciphersuite: Ciphersuite,
38 membership_key: &[u8],
39 auth_content_tbm: &FrankenAuthenticatedContentTbm,
40) -> VLBytes {
41 let serialized_auth_content_tbm = &auth_content_tbm.tls_serialize_detached().unwrap();
42 crypto
43 .hmac(
44 ciphersuite.hash_algorithm(),
45 membership_key, serialized_auth_content_tbm, )
48 .unwrap()
49 .as_slice()
50 .into()
51}
52
53pub fn sign_with_label(signer: &impl Signer, label: &[u8], msg: &[u8]) -> Vec<u8> {
55 let data = FrankenSignContent::new(label, msg)
56 .tls_serialize_detached()
57 .unwrap();
58 signer.sign(&data).unwrap()
59}
60
61#[derive(Debug, Clone, PartialEq, Eq, TlsSerialize, TlsSize)]
62pub struct FrankenSignContent<'a> {
63 label: Vec<u8>,
64 content: &'a [u8],
65}
66
67impl<'a> FrankenSignContent<'a> {
68 pub fn new(label: &[u8], content: &'a [u8]) -> Self {
69 let mut tagged_label = b"MLS 1.0 ".to_vec();
70 tagged_label.extend_from_slice(label);
71
72 Self {
73 label: tagged_label,
74 content,
75 }
76 }
77}
78
79impl FrankenWelcome {
80 #[openmls_traits::maybe_async]
87 pub async fn open<Crypto, Storage>(
88 &self,
89 crypto: &Crypto,
90 storage: &Storage,
91 ) -> Result<(Ciphersuite, FrankenGroupSecrets, FrankenGroupInfo), WelcomeError<Storage::Error>>
92 where
93 Crypto: OpenMlsCrypto,
94 Storage: StorageProvider,
95 {
96 let ciphersuite = Ciphersuite::try_from(self.cipher_suite)
97 .expect("cannot open welcome with invalid ciphersuite");
98 crypto
99 .supports(ciphersuite)
100 .map_err(|_| WelcomeError::UnsupportedCiphersuite(ciphersuite))?;
101
102 let (key_material, egs) = self.find_decryptable_secret(storage).await?;
104 let group_secrets_plaintext = hpke::decrypt_with_label(
105 key_material.init_private_key(),
106 "Welcome",
107 self.encrypted_group_info.as_slice(),
108 &egs.encrypted_group_secrets.clone().into(),
109 ciphersuite,
110 crypto,
111 )
112 .map_err(|_| GroupSecretsError::DecryptionFailed)?;
113 let group_secrets = FrankenGroupSecrets::tls_deserialize_exact(group_secrets_plaintext)
114 .map_err(|_| GroupSecretsError::Malformed)?;
115
116 let (welcome_key, welcome_nonce) = group_secrets.welcome_keys(
118 ciphersuite,
119 group_secrets
120 .psk_secret(ciphersuite, crypto, storage)
121 .await?,
122 crypto,
123 )?;
124
125 let group_info: GroupInfo = VerifiableGroupInfo::try_from_ciphertext(
127 &welcome_key,
128 &welcome_nonce,
129 self.encrypted_group_info.as_slice(),
130 &[],
131 crypto,
132 )?
133 .into();
134
135 Ok((ciphersuite, group_secrets, group_info.into()))
136 }
137
138 #[openmls_traits::maybe_async]
143 pub async fn seal<Crypto, Storage>(
144 ciphersuite: Ciphersuite,
145 group_secrets: &FrankenGroupSecrets,
146 group_info: &FrankenGroupInfo,
147 signer: &impl Signer,
148 invited_members: &[(FrankenKeyPackage, Option<FrankenPathSecret>)],
149 crypto: &Crypto,
150 storage: &Storage,
151 ) -> Result<Self, CreateCommitError>
152 where
153 Crypto: OpenMlsCrypto,
154 Storage: StorageProvider,
155 {
156 let mut group_info = group_info.clone();
158 group_info.resign(signer);
159
160 let psk_secret = group_secrets
162 .psk_secret(ciphersuite, crypto, storage)
163 .await?;
164 let (welcome_key, welcome_nonce) =
165 group_secrets.welcome_keys(ciphersuite, psk_secret, crypto)?;
166 let encrypted_group_info = welcome_key
167 .aead_seal(
168 crypto,
169 group_info
170 .tls_serialize_detached()
171 .map_err(LibraryError::missing_bound_check)?
172 .as_slice(),
173 &[],
174 &welcome_nonce,
175 )
176 .map_err(LibraryError::unexpected_crypto_error)?;
177
178 let psk_ids = group_secrets.psk_ids();
180 let joiner_secret = group_secrets.joiner_secret.clone().into();
181 let encrypted_group_secrets: Vec<FrankenEncryptedGroupSecrets> = invited_members
182 .iter()
183 .map(|invited_member| {
184 let (invited_key_package, path_secret) = invited_member;
185
186 let path_secret = path_secret.as_ref().map(|path_secret| PathSecret {
187 path_secret: Secret::from_slice(path_secret.as_slice()),
188 });
189 let encoded_group_secrets =
190 GroupSecrets::new_encoded(&joiner_secret, path_secret.as_ref(), &psk_ids)
191 .unwrap();
192
193 let key_package: KeyPackage = invited_key_package.clone().into();
194 let ciphertext = hpke::encrypt_with_label(
195 key_package.hpke_init_key().as_slice(),
196 "Welcome",
197 encrypted_group_info.as_slice(),
198 &encoded_group_secrets,
199 key_package.ciphersuite(),
200 crypto,
201 )
202 .unwrap();
203
204 FrankenEncryptedGroupSecrets {
205 new_member: key_package.hash_ref(crypto).unwrap().as_slice().into(),
206 encrypted_group_secrets: ciphertext.into(),
207 }
208 })
209 .collect();
210
211 Ok(Self {
213 cipher_suite: ciphersuite.into(),
214 secrets: encrypted_group_secrets,
215 encrypted_group_info: encrypted_group_info.into(),
216 })
217 }
218
219 #[openmls_traits::maybe_async]
223 pub(crate) async fn find_decryptable_secret<Storage>(
224 &self,
225 storage: &Storage,
226 ) -> Result<(WelcomeKeyMaterial, &FrankenEncryptedGroupSecrets), WelcomeError<Storage::Error>>
227 where
228 Storage: StorageProvider,
229 {
230 for egs in self.secrets.as_slice() {
232 let hash_ref = &HashReference::from_slice(egs.new_member.as_slice());
233 if let Some(key_package_bundle) = storage
234 .key_package(hash_ref)
235 .await
236 .map_err(WelcomeError::StorageError)?
237 {
238 return Ok((
239 WelcomeKeyMaterial::with_key_package_bundle(key_package_bundle),
240 egs,
241 ));
242 }
243 }
244 Err(WelcomeError::NoMatchingKeyPackage)
245 }
246
247 #[openmls_traits::maybe_async]
251 pub async fn with_sealed_update<F, InvitedProvider>(
252 &self,
253 signer: &impl Signer,
254 signer_provider: &impl OpenMlsProvider,
255 invited_key_package: &KeyPackage,
256 invited_provider: &InvitedProvider,
257 f: F,
258 ) -> Self
259 where
260 F: FnOnce(&mut Ciphersuite, &mut FrankenGroupSecrets, &mut FrankenGroupInfo),
261 InvitedProvider: OpenMlsProvider,
262 {
263 let (mut ciphersuite, mut group_secrets, mut group_info) = self
264 .open(invited_provider.crypto(), invited_provider.storage())
265 .await
266 .expect("failed to open FrankenWelcome");
267
268 f(&mut ciphersuite, &mut group_secrets, &mut group_info);
269
270 FrankenWelcome::seal(
271 ciphersuite,
272 &group_secrets,
273 &group_info,
274 signer,
275 &[(
276 invited_key_package.clone().into(),
277 group_secrets.path_secret.clone(),
278 )],
279 signer_provider.crypto(),
280 signer_provider.storage(),
281 )
282 .await
283 .expect("failed to seal FrankenWelcome")
284 }
285}
286
287impl FrankenGroupSecrets {
288 pub fn psk_ids(&self) -> Vec<PreSharedKeyId> {
289 self.psks.iter().clone().map(Into::into).collect()
290 }
291
292 #[openmls_traits::maybe_async]
293 pub async fn psk_secret<Crypto, Storage>(
294 &self,
295 ciphersuite: Ciphersuite,
296 crypto: &Crypto,
297 storage: &Storage,
298 ) -> Result<PskSecret, PskError>
299 where
300 Crypto: OpenMlsCrypto,
301 Storage: StorageProvider,
302 {
303 let psk_ids = self.psk_ids();
304 let psks = load_psks(
305 storage,
306 &ResumptionPskStore::new(0),
308 psk_ids.as_slice(),
309 )
310 .await?;
311 let psk_secret = PskSecret::new(crypto, ciphersuite, psks)?;
312 Ok(psk_secret)
313 }
314
315 pub(crate) fn welcome_keys<Crypto>(
316 &self,
317 ciphersuite: Ciphersuite,
318 psk_secret: PskSecret,
319 crypto: &Crypto,
320 ) -> Result<(AeadKey, AeadNonce), LibraryError>
321 where
322 Crypto: OpenMlsCrypto,
323 {
324 let joiner_secret = self.joiner_secret.clone().into();
325 let key_schedule = KeySchedule::init(ciphersuite, crypto, &joiner_secret, psk_secret)
326 .map_err(|_| LibraryError::custom("Using the key schedule in the wrong state"))?;
327 let welcome_secret = key_schedule.welcome(crypto, ciphersuite).unwrap();
328 let (welcome_key, welcome_nonce) = welcome_secret
329 .derive_welcome_key_nonce(crypto, ciphersuite)
330 .map_err(LibraryError::unexpected_crypto_error)?;
331 Ok((welcome_key, welcome_nonce))
332 }
333}
334
335#[cfg(test)]
336mod tests {
337 use super::{super::FrankenExtension, *};
338 use crate::group::{
339 mls_group::tests_and_kats::utils::{setup_alice_group, setup_client},
340 StagedWelcome,
341 };
342
343 #[openmls_test::openmls_test]
344 fn test_reseal_franken_group_info() {
345 let alice_provider = &Provider::default();
347 let bob_provider = &Provider::default();
348 let (mut alice_group, _alice_credential, alice_signer, _alice_pk) =
349 setup_alice_group(ciphersuite, alice_provider);
350
351 let (_bob_credential, bob_kpb, _bob_signer, _bob_pk) =
352 setup_client("Bob", ciphersuite, bob_provider);
353 let message_bundle = alice_group
354 .commit_builder()
355 .propose_adds([bob_kpb.key_package.clone()])
356 .load_psks(alice_provider.storage())
357 .unwrap()
358 .use_ratchet_tree_extension(true)
359 .build(
360 alice_provider.rand(),
361 alice_provider.crypto(),
362 &alice_signer,
363 |_proposal| true,
364 )
365 .unwrap()
366 .stage_commit(alice_provider)
367 .unwrap();
368
369 let welcome: FrankenWelcome = message_bundle.welcome().unwrap().clone().into();
371 let opened = welcome.open(bob_provider.crypto(), bob_provider.storage());
372 let (_ciphersuite, group_secrets, mut group_info) = opened.unwrap();
373
374 group_info.extensions.push(FrankenExtension::Unknown(
377 0xf000,
378 b"Test extension modifying group_info".into(),
379 ));
380
381 let resealed = FrankenWelcome::seal(
382 ciphersuite,
383 &group_secrets,
384 &group_info,
385 &alice_signer,
386 &[(
387 bob_kpb.key_package.clone().into(),
388 group_secrets.path_secret.clone(),
389 )],
390 alice_provider.crypto(),
391 alice_provider.storage(),
392 );
393
394 let staged_welcome = StagedWelcome::new_from_welcome(
396 bob_provider,
397 alice_group.configuration(),
398 resealed.unwrap().into(),
399 None,
400 )
401 .expect("expected valid join from valid welcome");
402
403 let _bob_group = staged_welcome
404 .into_group(bob_provider)
405 .expect("expected valid group from join");
406 }
407
408 #[openmls_test::openmls_test]
409 fn test_reseal_helper_franken_group_info() {
410 let alice_provider = &Provider::default();
412 let bob_provider = &Provider::default();
413 let (mut alice_group, _alice_credential, alice_signer, _alice_pk) =
414 setup_alice_group(ciphersuite, alice_provider);
415
416 let (_bob_credential, bob_kpb, _bob_signer, _bob_pk) =
417 setup_client("Bob", ciphersuite, bob_provider);
418 let message_bundle = alice_group
419 .commit_builder()
420 .propose_adds([bob_kpb.key_package.clone()])
421 .load_psks(alice_provider.storage())
422 .unwrap()
423 .use_ratchet_tree_extension(true)
424 .build(
425 alice_provider.rand(),
426 alice_provider.crypto(),
427 &alice_signer,
428 |_proposal| true,
429 )
430 .unwrap()
431 .stage_commit(alice_provider)
432 .unwrap();
433
434 let welcome: FrankenWelcome = message_bundle.welcome().unwrap().clone().into();
436 let welcome = welcome.with_sealed_update(
437 &alice_signer,
438 alice_provider,
439 &bob_kpb.key_package,
440 bob_provider,
441 |_, _, group_info| {
444 group_info.extensions.push(FrankenExtension::Unknown(
445 0xf000,
446 b"Test extension modifying group_info".into(),
447 ));
448 },
449 );
450
451 let staged_welcome = StagedWelcome::new_from_welcome(
453 bob_provider,
454 alice_group.configuration(),
455 welcome.into(),
456 None,
457 )
458 .expect("expected valid join from valid welcome");
459
460 let _bob_group = staged_welcome
461 .into_group(bob_provider)
462 .expect("expected valid group from join");
463 }
464}