1use hash_ref::HashReference;
7use openmls_traits::{
8 crypto::OpenMlsCrypto,
9 storage::StorageProvider,
10 types::{Ciphersuite, HpkeCiphertext, HpkeKeyPair},
11};
12use serde::{Deserialize, Serialize};
13use thiserror::Error;
14use tls_codec::{Deserialize as TlsDeserializeTrait, Serialize as TlsSerializeTrait, *};
15
16#[cfg(all(test, feature = "generate-kats"))]
17use crate::schedule::psk::{ExternalPsk, Psk};
18use crate::{
19 ciphersuite::{hash_ref::KeyPackageRef, *},
20 credentials::CredentialWithKey,
21 error::LibraryError,
22 framing::SenderContext,
23 group::{errors::ValidationError, LeafNodeLifetimePolicy, WelcomeError, WelcomeKeyMaterial},
24 schedule::{psk::PreSharedKeyId, JoinerSecret},
25 storage::OpenMlsProvider,
26 treesync::{
27 node::{
28 encryption_keys::{EncryptionKey, EncryptionKeyPair, EncryptionPrivateKey},
29 leaf_node::TreePosition,
30 },
31 treekem::{UpdatePath, UpdatePathIn},
32 },
33 versions::ProtocolVersion,
34};
35#[cfg(all(test, feature = "generate-kats"))]
36use openmls_traits::random::OpenMlsRand;
37
38pub(crate) mod codec;
39pub mod external_proposals;
40pub mod group_info;
41pub mod proposals;
42pub mod proposals_in;
43
44#[cfg(test)]
45mod tests;
46
47use self::{proposals::*, proposals_in::ProposalOrRefIn};
48
49#[derive(
64 Clone,
65 Debug,
66 Eq,
67 PartialEq,
68 TlsDeserialize,
69 TlsDeserializeBytes,
70 TlsSerialize,
71 TlsSize,
72 serde::Serialize,
73 serde::Deserialize,
74)]
75pub struct Welcome {
76 cipher_suite: Ciphersuite,
77 secrets: Vec<EncryptedGroupSecrets>,
78 encrypted_group_info: VLBytes,
79}
80
81impl Welcome {
82 pub(crate) fn new(
85 cipher_suite: Ciphersuite,
86 secrets: Vec<EncryptedGroupSecrets>,
87 encrypted_group_info: Vec<u8>,
88 ) -> Self {
89 Self {
90 cipher_suite,
91 secrets,
92 encrypted_group_info: encrypted_group_info.into(),
93 }
94 }
95
96 pub(crate) fn find_encrypted_group_secret(
97 &self,
98 hash_ref: HashReference,
99 ) -> Option<&EncryptedGroupSecrets> {
100 self.secrets()
101 .iter()
102 .find(|egs| hash_ref == egs.new_member())
103 }
104
105 pub fn ciphersuite(&self) -> Ciphersuite {
107 self.cipher_suite
108 }
109
110 pub fn secrets(&self) -> &[EncryptedGroupSecrets] {
112 self.secrets.as_slice()
113 }
114
115 pub(crate) fn encrypted_group_info(&self) -> &[u8] {
117 self.encrypted_group_info.as_slice()
118 }
119
120 #[cfg(test)]
122 pub fn set_encrypted_group_info(&mut self, encrypted_group_info: Vec<u8>) {
123 self.encrypted_group_info = encrypted_group_info.into();
124 }
125
126 #[openmls_traits::maybe_async]
132 pub async fn resolve_own_key_material<Provider: OpenMlsProvider>(
133 &self,
134 provider: &Provider,
135 ) -> Result<Option<WelcomeKeyMaterial>, WelcomeError<Provider::StorageError>> {
136 for egs in &self.secrets {
137 let hash_ref = egs.new_member();
138
139 if let Some(bundle) = provider
140 .storage()
141 .key_package(&hash_ref)
142 .await
143 .map_err(WelcomeError::StorageError)?
144 {
145 return Ok(Some(WelcomeKeyMaterial::with_key_package_bundle(bundle)));
146 }
147
148 #[cfg(feature = "virtual-clients-draft")]
149 if let Some(material) =
150 crate::group::resolve_vc_welcome_material(provider, self.ciphersuite(), &hash_ref)
151 .await?
152 {
153 return Ok(Some(WelcomeKeyMaterial::with_vc_welcome_material(material)));
154 }
155 }
156
157 Ok(None)
158 }
159}
160
161#[derive(
165 Clone,
166 Debug,
167 Eq,
168 PartialEq,
169 TlsDeserialize,
170 TlsDeserializeBytes,
171 TlsSerialize,
172 TlsSize,
173 serde::Serialize,
174 serde::Deserialize,
175)]
176pub struct EncryptedGroupSecrets {
177 new_member: KeyPackageRef,
179 encrypted_group_secrets: HpkeCiphertext,
181}
182
183impl EncryptedGroupSecrets {
184 pub fn new(new_member: KeyPackageRef, encrypted_group_secrets: HpkeCiphertext) -> Self {
186 Self {
187 new_member,
188 encrypted_group_secrets,
189 }
190 }
191
192 pub fn new_member(&self) -> KeyPackageRef {
194 self.new_member.clone()
195 }
196
197 pub(crate) fn encrypted_group_secrets(&self) -> &HpkeCiphertext {
199 &self.encrypted_group_secrets
200 }
201}
202
203#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, TlsSerialize, TlsSize)]
222pub(crate) struct Commit {
223 pub(crate) proposals: Vec<ProposalOrRef>,
224 pub(crate) path: Option<UpdatePath>,
225}
226
227impl Commit {
228 #[cfg(test)]
230 pub fn has_path(&self) -> bool {
231 self.path.is_some()
232 }
233
234 #[cfg(test)]
236 pub(crate) fn path(&self) -> &Option<UpdatePath> {
237 &self.path
238 }
239}
240
241#[derive(
242 Debug,
243 PartialEq,
244 Clone,
245 Serialize,
246 Deserialize,
247 TlsDeserialize,
248 TlsDeserializeBytes,
249 TlsSerialize,
250 TlsSize,
251)]
252pub(crate) struct CommitIn {
253 proposals: Vec<ProposalOrRefIn>,
254 path: Option<UpdatePathIn>,
255}
256
257impl CommitIn {
258 #[cfg(feature = "extensions-draft")]
261 pub(crate) fn unverified_proposals(&self) -> &[ProposalOrRefIn] {
262 &self.proposals
263 }
264
265 pub(crate) fn unverified_credential(&self) -> Option<CredentialWithKey> {
266 self.path.as_ref().map(|p| {
267 let credential = p.leaf_node().credential().clone();
268 let pk = p.leaf_node().signature_key().clone();
269 CredentialWithKey {
270 credential,
271 signature_key: pk,
272 }
273 })
274 }
275
276 pub(crate) fn validate(
278 self,
279 ciphersuite: Ciphersuite,
280 crypto: &impl OpenMlsCrypto,
281 sender_context: SenderContext,
282 protocol_version: ProtocolVersion,
283 lifetime_policy: LeafNodeLifetimePolicy,
284 ) -> Result<Commit, ValidationError> {
285 let proposals = self
286 .proposals
287 .into_iter()
288 .map(|p| p.validate(crypto, ciphersuite, protocol_version, lifetime_policy))
289 .collect::<Result<Vec<_>, _>>()?;
290
291 let path = if let Some(path) = self.path {
292 let tree_position = match sender_context {
293 SenderContext::Member((group_id, leaf_index)) => {
294 TreePosition::new(group_id, leaf_index)
295 }
296 SenderContext::ExternalCommit {
297 group_id,
298 leftmost_blank_index,
299 self_removes_in_store,
300 } => {
301 let former_sender_index = proposals.iter().find_map(|p| {
304 p.as_proposal()
305 .and_then(|p| p.as_remove())
306 .map(|r| r.removed())
307 });
308
309 let self_removed_indices =
312 self_removes_in_store.into_iter().filter_map(|self_remove| {
313 proposals.iter().find_map(|committed_p| {
314 committed_p.as_reference().and_then(|committed_p_ref| {
315 (&self_remove.proposal_ref == committed_p_ref)
316 .then_some(self_remove.sender)
317 })
318 })
319 });
320
321 let new_leaf_index = [leftmost_blank_index]
322 .into_iter()
323 .chain(former_sender_index)
324 .chain(self_removed_indices)
325 .min()
326 .ok_or_else(|| {
327 ValidationError::LibraryError(LibraryError::custom(
328 "The iterator should have at least one element.",
329 ))
330 })?;
331
332 TreePosition::new(group_id, new_leaf_index)
333 }
334 };
335 Some(path.into_verified(ciphersuite, crypto, tree_position)?)
336 } else {
337 None
338 };
339 Ok(Commit { proposals, path })
340 }
341}
342
343#[cfg(any(feature = "test-utils", test))]
346impl From<CommitIn> for Commit {
347 fn from(commit: CommitIn) -> Self {
348 Self {
349 proposals: commit.proposals.into_iter().map(Into::into).collect(),
350 path: commit.path.map(Into::into),
351 }
352 }
353}
354
355impl From<Commit> for CommitIn {
356 fn from(commit: Commit) -> Self {
357 Self {
358 proposals: commit.proposals.into_iter().map(Into::into).collect(),
359 path: commit.path.map(Into::into),
360 }
361 }
362}
363
364#[derive(
367 Debug,
368 PartialEq,
369 Clone,
370 Serialize,
371 Deserialize,
372 TlsDeserialize,
373 TlsDeserializeBytes,
374 TlsSerialize,
375 TlsSize,
376)]
377pub struct ConfirmationTag(pub(crate) Mac);
378
379#[derive(
389 Debug, Serialize, Deserialize, TlsSerialize, TlsDeserialize, TlsDeserializeBytes, TlsSize,
390)]
391#[cfg_attr(any(feature = "test-utils", test), derive(PartialEq, Clone))]
392pub(crate) struct PathSecret {
393 pub(crate) path_secret: Secret,
394}
395
396impl From<Secret> for PathSecret {
397 fn from(path_secret: Secret) -> Self {
398 Self { path_secret }
399 }
400}
401
402impl PathSecret {
403 pub(crate) fn derive_key_pair(
405 &self,
406 crypto: &impl OpenMlsCrypto,
407 ciphersuite: Ciphersuite,
408 ) -> Result<EncryptionKeyPair, LibraryError> {
409 let node_secret = self
410 .path_secret
411 .kdf_expand_label(crypto, ciphersuite, "node", &[], ciphersuite.hash_length())
412 .map_err(LibraryError::unexpected_crypto_error)?;
413 let HpkeKeyPair { public, private } = crypto
414 .derive_hpke_keypair(ciphersuite.hpke_config(), node_secret.as_slice())
415 .map_err(LibraryError::unexpected_crypto_error)?;
416
417 Ok((HpkePublicKey::from(public), private).into())
418 }
419
420 pub(crate) fn derive_path_secret(
422 &self,
423 crypto: &impl OpenMlsCrypto,
424 ciphersuite: Ciphersuite,
425 ) -> Result<Self, LibraryError> {
426 let path_secret = self
427 .path_secret
428 .kdf_expand_label(crypto, ciphersuite, "path", &[], ciphersuite.hash_length())
429 .map_err(LibraryError::unexpected_crypto_error)?;
430 Ok(Self { path_secret })
431 }
432
433 pub(crate) fn encrypt(
436 &self,
437 crypto: &impl OpenMlsCrypto,
438 ciphersuite: Ciphersuite,
439 public_key: &EncryptionKey,
440 group_context: &[u8],
441 ) -> Result<HpkeCiphertext, LibraryError> {
442 public_key.encrypt(
443 crypto,
444 ciphersuite,
445 group_context,
446 self.path_secret.as_slice(),
447 )
448 }
449
450 pub(crate) fn secret(self) -> Secret {
452 self.path_secret
453 }
454
455 pub(crate) fn decrypt(
462 crypto: &impl OpenMlsCrypto,
463 ciphersuite: Ciphersuite,
464 ciphertext: &HpkeCiphertext,
465 private_key: &EncryptionPrivateKey,
466 group_context: &[u8],
467 ) -> Result<PathSecret, PathSecretError> {
468 private_key
470 .decrypt(crypto, ciphersuite, ciphertext, group_context)
471 .map(|path_secret| Self { path_secret })
472 .map_err(|e| e.into())
473 }
474}
475
476#[derive(Error, Debug, PartialEq, Clone)]
478pub(crate) enum PathSecretError {
479 #[error(transparent)]
481 DecryptionError(#[from] hpke::Error),
482}
483
484#[derive(Debug, TlsDeserialize, TlsDeserializeBytes, TlsSize)]
495pub(crate) struct GroupSecrets {
496 pub(crate) joiner_secret: JoinerSecret,
497 pub(crate) path_secret: Option<PathSecret>,
498 pub(crate) psks: Vec<PreSharedKeyId>,
499}
500
501#[derive(TlsSerialize, TlsSize)]
502struct EncodedGroupSecrets<'a> {
503 pub(crate) joiner_secret: &'a JoinerSecret,
504 pub(crate) path_secret: Option<&'a PathSecret>,
505 pub(crate) psks: &'a [PreSharedKeyId],
506}
507
508#[derive(Error, Debug, PartialEq, Clone)]
510pub enum GroupSecretsError {
511 #[error("Decryption failed.")]
513 DecryptionFailed,
514 #[error("Malformed.")]
516 Malformed,
517}
518
519impl GroupSecrets {
520 pub(crate) fn try_from_ciphertext(
522 skey: &HpkePrivateKey,
523 ciphertext: &HpkeCiphertext,
524 context: &[u8],
525 ciphersuite: Ciphersuite,
526 crypto: &impl OpenMlsCrypto,
527 ) -> Result<Self, GroupSecretsError> {
528 let group_secrets_plaintext =
529 hpke::decrypt_with_label(skey, "Welcome", context, ciphertext, ciphersuite, crypto)
530 .map_err(|_| GroupSecretsError::DecryptionFailed)?;
531
532 let group_secrets = GroupSecrets::tls_deserialize_exact(group_secrets_plaintext)
534 .map_err(|_| GroupSecretsError::Malformed)?;
535
536 Ok(group_secrets)
537 }
538
539 pub(crate) fn new_encoded<'a>(
541 joiner_secret: &JoinerSecret,
542 path_secret: Option<&'a PathSecret>,
543 psks: &'a [PreSharedKeyId],
544 ) -> Result<Vec<u8>, tls_codec::Error> {
545 EncodedGroupSecrets {
546 joiner_secret,
547 path_secret,
548 psks,
549 }
550 .tls_serialize_detached()
551 }
552}
553
554#[cfg(all(test, feature = "generate-kats"))]
555impl GroupSecrets {
556 pub fn random_encoded(
557 ciphersuite: Ciphersuite,
558 rng: &impl OpenMlsRand,
559 ) -> Result<Vec<u8>, tls_codec::Error> {
560 let psk_id = PreSharedKeyId::new(
561 ciphersuite,
562 rng,
563 Psk::External(ExternalPsk::new(
564 rng.random_vec(ciphersuite.hash_length())
565 .expect("Not enough randomness."),
566 )),
567 )
568 .expect("An unexpected error occurred.");
569 let psks = vec![psk_id];
570
571 GroupSecrets::new_encoded(
572 &JoinerSecret::random(ciphersuite, rng),
573 Some(&PathSecret {
574 path_secret: Secret::random(ciphersuite, rng).expect("Not enough randomness."),
575 }),
576 &psks,
577 )
578 }
579}