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, 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 pub fn resolve_own_key_material<Provider: OpenMlsProvider>(
132 &self,
133 provider: &Provider,
134 ) -> Result<Option<WelcomeKeyMaterial>, WelcomeError<Provider::StorageError>> {
135 for egs in &self.secrets {
136 let hash_ref = egs.new_member();
137
138 if let Some(bundle) = provider
139 .storage()
140 .key_package(&hash_ref)
141 .map_err(WelcomeError::StorageError)?
142 {
143 return Ok(Some(WelcomeKeyMaterial::with_key_package_bundle(bundle)));
144 }
145
146 #[cfg(feature = "virtual-clients-draft")]
147 if let Some(material) =
148 crate::group::resolve_vc_welcome_material(provider, self.ciphersuite(), &hash_ref)?
149 {
150 return Ok(Some(WelcomeKeyMaterial::with_vc_welcome_material(material)));
151 }
152 }
153
154 Ok(None)
155 }
156}
157
158#[derive(
162 Clone,
163 Debug,
164 Eq,
165 PartialEq,
166 TlsDeserialize,
167 TlsDeserializeBytes,
168 TlsSerialize,
169 TlsSize,
170 serde::Serialize,
171 serde::Deserialize,
172)]
173pub struct EncryptedGroupSecrets {
174 new_member: KeyPackageRef,
176 encrypted_group_secrets: HpkeCiphertext,
178}
179
180impl EncryptedGroupSecrets {
181 pub fn new(new_member: KeyPackageRef, encrypted_group_secrets: HpkeCiphertext) -> Self {
183 Self {
184 new_member,
185 encrypted_group_secrets,
186 }
187 }
188
189 pub fn new_member(&self) -> KeyPackageRef {
191 self.new_member.clone()
192 }
193
194 pub(crate) fn encrypted_group_secrets(&self) -> &HpkeCiphertext {
196 &self.encrypted_group_secrets
197 }
198}
199
200#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, TlsSerialize, TlsSize)]
219pub(crate) struct Commit {
220 pub(crate) proposals: Vec<ProposalOrRef>,
221 pub(crate) path: Option<UpdatePath>,
222}
223
224impl Commit {
225 #[cfg(test)]
227 pub fn has_path(&self) -> bool {
228 self.path.is_some()
229 }
230
231 #[cfg(test)]
233 pub(crate) fn path(&self) -> &Option<UpdatePath> {
234 &self.path
235 }
236}
237
238#[derive(
239 Debug,
240 PartialEq,
241 Clone,
242 Serialize,
243 Deserialize,
244 TlsDeserialize,
245 TlsDeserializeBytes,
246 TlsSerialize,
247 TlsSize,
248)]
249pub(crate) struct CommitIn {
250 proposals: Vec<ProposalOrRefIn>,
251 path: Option<UpdatePathIn>,
252}
253
254impl CommitIn {
255 #[cfg(feature = "extensions-draft")]
258 pub(crate) fn unverified_proposals(&self) -> &[ProposalOrRefIn] {
259 &self.proposals
260 }
261
262 pub(crate) fn unverified_credential(&self) -> Option<CredentialWithKey> {
263 self.path.as_ref().map(|p| {
264 let credential = p.leaf_node().credential().clone();
265 let pk = p.leaf_node().signature_key().clone();
266 CredentialWithKey {
267 credential,
268 signature_key: pk,
269 }
270 })
271 }
272
273 pub(crate) fn validate(
275 self,
276 ciphersuite: Ciphersuite,
277 crypto: &impl OpenMlsCrypto,
278 sender_context: SenderContext,
279 protocol_version: ProtocolVersion,
280 ) -> Result<Commit, ValidationError> {
281 let proposals = self
282 .proposals
283 .into_iter()
284 .map(|p| p.validate(crypto, ciphersuite, protocol_version))
285 .collect::<Result<Vec<_>, _>>()?;
286
287 let path = if let Some(path) = self.path {
288 let tree_position = match sender_context {
289 SenderContext::Member((group_id, leaf_index)) => {
290 TreePosition::new(group_id, leaf_index)
291 }
292 SenderContext::ExternalCommit {
293 group_id,
294 leftmost_blank_index,
295 self_removes_in_store,
296 } => {
297 let former_sender_index = proposals.iter().find_map(|p| {
300 p.as_proposal()
301 .and_then(|p| p.as_remove())
302 .map(|r| r.removed())
303 });
304
305 let self_removed_indices =
308 self_removes_in_store.into_iter().filter_map(|self_remove| {
309 proposals.iter().find_map(|committed_p| {
310 committed_p.as_reference().and_then(|committed_p_ref| {
311 (&self_remove.proposal_ref == committed_p_ref)
312 .then_some(self_remove.sender)
313 })
314 })
315 });
316
317 let new_leaf_index = [leftmost_blank_index]
318 .into_iter()
319 .chain(former_sender_index)
320 .chain(self_removed_indices)
321 .min()
322 .ok_or_else(|| {
323 ValidationError::LibraryError(LibraryError::custom(
324 "The iterator should have at least one element.",
325 ))
326 })?;
327
328 TreePosition::new(group_id, new_leaf_index)
329 }
330 };
331 Some(path.into_verified(ciphersuite, crypto, tree_position)?)
332 } else {
333 None
334 };
335 Ok(Commit { proposals, path })
336 }
337}
338
339#[cfg(any(feature = "test-utils", test))]
342impl From<CommitIn> for Commit {
343 fn from(commit: CommitIn) -> Self {
344 Self {
345 proposals: commit.proposals.into_iter().map(Into::into).collect(),
346 path: commit.path.map(Into::into),
347 }
348 }
349}
350
351impl From<Commit> for CommitIn {
352 fn from(commit: Commit) -> Self {
353 Self {
354 proposals: commit.proposals.into_iter().map(Into::into).collect(),
355 path: commit.path.map(Into::into),
356 }
357 }
358}
359
360#[derive(
363 Debug,
364 PartialEq,
365 Clone,
366 Serialize,
367 Deserialize,
368 TlsDeserialize,
369 TlsDeserializeBytes,
370 TlsSerialize,
371 TlsSize,
372)]
373pub struct ConfirmationTag(pub(crate) Mac);
374
375#[derive(
385 Debug, Serialize, Deserialize, TlsSerialize, TlsDeserialize, TlsDeserializeBytes, TlsSize,
386)]
387#[cfg_attr(any(feature = "test-utils", test), derive(PartialEq, Clone))]
388pub(crate) struct PathSecret {
389 pub(crate) path_secret: Secret,
390}
391
392impl From<Secret> for PathSecret {
393 fn from(path_secret: Secret) -> Self {
394 Self { path_secret }
395 }
396}
397
398impl PathSecret {
399 pub(crate) fn derive_key_pair(
401 &self,
402 crypto: &impl OpenMlsCrypto,
403 ciphersuite: Ciphersuite,
404 ) -> Result<EncryptionKeyPair, LibraryError> {
405 let node_secret = self
406 .path_secret
407 .kdf_expand_label(crypto, ciphersuite, "node", &[], ciphersuite.hash_length())
408 .map_err(LibraryError::unexpected_crypto_error)?;
409 let HpkeKeyPair { public, private } = crypto
410 .derive_hpke_keypair(ciphersuite.hpke_config(), node_secret.as_slice())
411 .map_err(LibraryError::unexpected_crypto_error)?;
412
413 Ok((HpkePublicKey::from(public), private).into())
414 }
415
416 pub(crate) fn derive_path_secret(
418 &self,
419 crypto: &impl OpenMlsCrypto,
420 ciphersuite: Ciphersuite,
421 ) -> Result<Self, LibraryError> {
422 let path_secret = self
423 .path_secret
424 .kdf_expand_label(crypto, ciphersuite, "path", &[], ciphersuite.hash_length())
425 .map_err(LibraryError::unexpected_crypto_error)?;
426 Ok(Self { path_secret })
427 }
428
429 pub(crate) fn encrypt(
432 &self,
433 crypto: &impl OpenMlsCrypto,
434 ciphersuite: Ciphersuite,
435 public_key: &EncryptionKey,
436 group_context: &[u8],
437 ) -> Result<HpkeCiphertext, LibraryError> {
438 public_key.encrypt(
439 crypto,
440 ciphersuite,
441 group_context,
442 self.path_secret.as_slice(),
443 )
444 }
445
446 pub(crate) fn secret(self) -> Secret {
448 self.path_secret
449 }
450
451 pub(crate) fn decrypt(
458 crypto: &impl OpenMlsCrypto,
459 ciphersuite: Ciphersuite,
460 ciphertext: &HpkeCiphertext,
461 private_key: &EncryptionPrivateKey,
462 group_context: &[u8],
463 ) -> Result<PathSecret, PathSecretError> {
464 private_key
466 .decrypt(crypto, ciphersuite, ciphertext, group_context)
467 .map(|path_secret| Self { path_secret })
468 .map_err(|e| e.into())
469 }
470}
471
472#[derive(Error, Debug, PartialEq, Clone)]
474pub(crate) enum PathSecretError {
475 #[error(transparent)]
477 DecryptionError(#[from] hpke::Error),
478}
479
480#[derive(Debug, TlsDeserialize, TlsDeserializeBytes, TlsSize)]
491pub(crate) struct GroupSecrets {
492 pub(crate) joiner_secret: JoinerSecret,
493 pub(crate) path_secret: Option<PathSecret>,
494 pub(crate) psks: Vec<PreSharedKeyId>,
495}
496
497#[derive(TlsSerialize, TlsSize)]
498struct EncodedGroupSecrets<'a> {
499 pub(crate) joiner_secret: &'a JoinerSecret,
500 pub(crate) path_secret: Option<&'a PathSecret>,
501 pub(crate) psks: &'a [PreSharedKeyId],
502}
503
504#[derive(Error, Debug, PartialEq, Clone)]
506pub enum GroupSecretsError {
507 #[error("Decryption failed.")]
509 DecryptionFailed,
510 #[error("Malformed.")]
512 Malformed,
513}
514
515impl GroupSecrets {
516 pub(crate) fn try_from_ciphertext(
518 skey: &HpkePrivateKey,
519 ciphertext: &HpkeCiphertext,
520 context: &[u8],
521 ciphersuite: Ciphersuite,
522 crypto: &impl OpenMlsCrypto,
523 ) -> Result<Self, GroupSecretsError> {
524 let group_secrets_plaintext =
525 hpke::decrypt_with_label(skey, "Welcome", context, ciphertext, ciphersuite, crypto)
526 .map_err(|_| GroupSecretsError::DecryptionFailed)?;
527
528 let group_secrets = GroupSecrets::tls_deserialize_exact(group_secrets_plaintext)
530 .map_err(|_| GroupSecretsError::Malformed)?;
531
532 Ok(group_secrets)
533 }
534
535 pub(crate) fn new_encoded<'a>(
537 joiner_secret: &JoinerSecret,
538 path_secret: Option<&'a PathSecret>,
539 psks: &'a [PreSharedKeyId],
540 ) -> Result<Vec<u8>, tls_codec::Error> {
541 EncodedGroupSecrets {
542 joiner_secret,
543 path_secret,
544 psks,
545 }
546 .tls_serialize_detached()
547 }
548}
549
550#[cfg(all(test, feature = "generate-kats"))]
551impl GroupSecrets {
552 pub fn random_encoded(
553 ciphersuite: Ciphersuite,
554 rng: &impl OpenMlsRand,
555 ) -> Result<Vec<u8>, tls_codec::Error> {
556 let psk_id = PreSharedKeyId::new(
557 ciphersuite,
558 rng,
559 Psk::External(ExternalPsk::new(
560 rng.random_vec(ciphersuite.hash_length())
561 .expect("Not enough randomness."),
562 )),
563 )
564 .expect("An unexpected error occurred.");
565 let psks = vec![psk_id];
566
567 GroupSecrets::new_encoded(
568 &JoinerSecret::random(ciphersuite, rng),
569 Some(&PathSecret {
570 path_secret: Secret::random(ciphersuite, rng).expect("Not enough randomness."),
571 }),
572 &psks,
573 )
574 }
575}