1use hash_ref::HashReference;
7use openmls_traits::{
8 crypto::OpenMlsCrypto,
9 types::{Ciphersuite, HpkeCiphertext, HpkeKeyPair},
10};
11use serde::{Deserialize, Serialize};
12use thiserror::Error;
13use tls_codec::{Deserialize as TlsDeserializeTrait, Serialize as TlsSerializeTrait, *};
14
15#[cfg(test)]
16use crate::schedule::psk::{ExternalPsk, Psk};
17use crate::{
18 ciphersuite::{hash_ref::KeyPackageRef, *},
19 credentials::CredentialWithKey,
20 error::LibraryError,
21 framing::SenderContext,
22 group::errors::ValidationError,
23 schedule::{psk::PreSharedKeyId, JoinerSecret},
24 treesync::{
25 node::{
26 encryption_keys::{EncryptionKey, EncryptionKeyPair, EncryptionPrivateKey},
27 leaf_node::TreePosition,
28 },
29 treekem::{UpdatePath, UpdatePathIn},
30 },
31 versions::ProtocolVersion,
32};
33#[cfg(test)]
34use openmls_traits::random::OpenMlsRand;
35
36pub(crate) mod codec;
37pub mod external_proposals;
38pub mod group_info;
39pub mod proposals;
40pub mod proposals_in;
41
42#[cfg(test)]
43mod tests;
44
45use self::{proposals::*, proposals_in::ProposalOrRefIn};
46
47#[derive(
62 Clone, Debug, Eq, PartialEq, TlsDeserialize, TlsDeserializeBytes, TlsSerialize, TlsSize,
63)]
64pub struct Welcome {
65 cipher_suite: Ciphersuite,
66 secrets: Vec<EncryptedGroupSecrets>,
67 encrypted_group_info: VLBytes,
68}
69
70impl Welcome {
71 pub(crate) fn new(
74 cipher_suite: Ciphersuite,
75 secrets: Vec<EncryptedGroupSecrets>,
76 encrypted_group_info: Vec<u8>,
77 ) -> Self {
78 Self {
79 cipher_suite,
80 secrets,
81 encrypted_group_info: encrypted_group_info.into(),
82 }
83 }
84
85 pub(crate) fn find_encrypted_group_secret(
86 &self,
87 hash_ref: HashReference,
88 ) -> Option<&EncryptedGroupSecrets> {
89 self.secrets()
90 .iter()
91 .find(|egs| hash_ref == egs.new_member())
92 }
93
94 pub(crate) fn ciphersuite(&self) -> Ciphersuite {
96 self.cipher_suite
97 }
98
99 pub fn secrets(&self) -> &[EncryptedGroupSecrets] {
101 self.secrets.as_slice()
102 }
103
104 pub(crate) fn encrypted_group_info(&self) -> &[u8] {
106 self.encrypted_group_info.as_slice()
107 }
108
109 #[cfg(test)]
111 pub fn set_encrypted_group_info(&mut self, encrypted_group_info: Vec<u8>) {
112 self.encrypted_group_info = encrypted_group_info.into();
113 }
114}
115
116#[derive(
120 Clone, Debug, Eq, PartialEq, TlsDeserialize, TlsDeserializeBytes, TlsSerialize, TlsSize,
121)]
122pub struct EncryptedGroupSecrets {
123 new_member: KeyPackageRef,
125 encrypted_group_secrets: HpkeCiphertext,
127}
128
129impl EncryptedGroupSecrets {
130 pub fn new(new_member: KeyPackageRef, encrypted_group_secrets: HpkeCiphertext) -> Self {
132 Self {
133 new_member,
134 encrypted_group_secrets,
135 }
136 }
137
138 pub fn new_member(&self) -> KeyPackageRef {
140 self.new_member.clone()
141 }
142
143 pub(crate) fn encrypted_group_secrets(&self) -> &HpkeCiphertext {
145 &self.encrypted_group_secrets
146 }
147}
148
149#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, TlsSerialize, TlsSize)]
168pub(crate) struct Commit {
169 pub(crate) proposals: Vec<ProposalOrRef>,
170 pub(crate) path: Option<UpdatePath>,
171}
172
173impl Commit {
174 #[cfg(test)]
176 pub fn has_path(&self) -> bool {
177 self.path.is_some()
178 }
179
180 #[cfg(test)]
182 pub(crate) fn path(&self) -> &Option<UpdatePath> {
183 &self.path
184 }
185}
186
187#[derive(
188 Debug,
189 PartialEq,
190 Clone,
191 Serialize,
192 Deserialize,
193 TlsDeserialize,
194 TlsDeserializeBytes,
195 TlsSerialize,
196 TlsSize,
197)]
198pub(crate) struct CommitIn {
199 proposals: Vec<ProposalOrRefIn>,
200 path: Option<UpdatePathIn>,
201}
202
203impl CommitIn {
204 pub(crate) fn unverified_credential(&self) -> Option<CredentialWithKey> {
205 self.path.as_ref().map(|p| {
206 let credential = p.leaf_node().credential().clone();
207 let pk = p.leaf_node().signature_key().clone();
208 CredentialWithKey {
209 credential,
210 signature_key: pk,
211 }
212 })
213 }
214
215 pub(crate) fn validate(
217 self,
218 ciphersuite: Ciphersuite,
219 crypto: &impl OpenMlsCrypto,
220 sender_context: SenderContext,
221 protocol_version: ProtocolVersion,
222 ) -> Result<Commit, ValidationError> {
223 let proposals = self
224 .proposals
225 .into_iter()
226 .map(|p| p.validate(crypto, ciphersuite, protocol_version))
227 .collect::<Result<Vec<_>, _>>()?;
228
229 let path = if let Some(path) = self.path {
230 let tree_position = match sender_context {
231 SenderContext::Member((group_id, leaf_index)) => {
232 TreePosition::new(group_id, leaf_index)
233 }
234 SenderContext::ExternalCommit {
235 group_id,
236 leftmost_blank_index,
237 self_removes_in_store,
238 } => {
239 let former_sender_index = proposals.iter().find_map(|p| {
242 let ProposalOrRef::Proposal(Proposal::Remove(r)) = p else {
243 return None;
244 };
245 Some(r.removed())
246 });
247
248 let self_removed_indices =
251 self_removes_in_store.into_iter().filter_map(|self_remove| {
252 proposals.iter().find_map(|committed_p| {
253 let ProposalOrRef::Reference(committed_p_ref) = committed_p else {
254 return None;
255 };
256 (self_remove.proposal_ref == *committed_p_ref)
257 .then_some(self_remove.sender)
258 })
259 });
260
261 let new_leaf_index = [leftmost_blank_index]
262 .into_iter()
263 .chain(former_sender_index)
264 .chain(self_removed_indices)
265 .min()
266 .ok_or(ValidationError::LibraryError(LibraryError::custom(
267 "The iterator should have at least one element.",
268 )))?;
269
270 TreePosition::new(group_id, new_leaf_index)
271 }
272 };
273 Some(path.into_verified(ciphersuite, crypto, tree_position)?)
274 } else {
275 None
276 };
277 Ok(Commit { proposals, path })
278 }
279}
280
281#[cfg(any(feature = "test-utils", test))]
284impl From<CommitIn> for Commit {
285 fn from(commit: CommitIn) -> Self {
286 Self {
287 proposals: commit.proposals.into_iter().map(Into::into).collect(),
288 path: commit.path.map(Into::into),
289 }
290 }
291}
292
293impl From<Commit> for CommitIn {
294 fn from(commit: Commit) -> Self {
295 Self {
296 proposals: commit.proposals.into_iter().map(Into::into).collect(),
297 path: commit.path.map(Into::into),
298 }
299 }
300}
301
302#[derive(
305 Debug,
306 PartialEq,
307 Clone,
308 Serialize,
309 Deserialize,
310 TlsDeserialize,
311 TlsDeserializeBytes,
312 TlsSerialize,
313 TlsSize,
314)]
315pub struct ConfirmationTag(pub(crate) Mac);
316
317#[derive(
327 Debug, Serialize, Deserialize, TlsSerialize, TlsDeserialize, TlsDeserializeBytes, TlsSize,
328)]
329#[cfg_attr(any(feature = "test-utils", test), derive(PartialEq, Clone))]
330pub(crate) struct PathSecret {
331 pub(crate) path_secret: Secret,
332}
333
334impl From<Secret> for PathSecret {
335 fn from(path_secret: Secret) -> Self {
336 Self { path_secret }
337 }
338}
339
340impl PathSecret {
341 pub(crate) fn derive_key_pair(
343 &self,
344 crypto: &impl OpenMlsCrypto,
345 ciphersuite: Ciphersuite,
346 ) -> Result<EncryptionKeyPair, LibraryError> {
347 let node_secret = self
348 .path_secret
349 .kdf_expand_label(crypto, ciphersuite, "node", &[], ciphersuite.hash_length())
350 .map_err(LibraryError::unexpected_crypto_error)?;
351 let HpkeKeyPair { public, private } = crypto
352 .derive_hpke_keypair(ciphersuite.hpke_config(), node_secret.as_slice())
353 .map_err(LibraryError::unexpected_crypto_error)?;
354
355 Ok((HpkePublicKey::from(public), private).into())
356 }
357
358 pub(crate) fn derive_path_secret(
360 &self,
361 crypto: &impl OpenMlsCrypto,
362 ciphersuite: Ciphersuite,
363 ) -> Result<Self, LibraryError> {
364 let path_secret = self
365 .path_secret
366 .kdf_expand_label(crypto, ciphersuite, "path", &[], ciphersuite.hash_length())
367 .map_err(LibraryError::unexpected_crypto_error)?;
368 Ok(Self { path_secret })
369 }
370
371 pub(crate) fn encrypt(
374 &self,
375 crypto: &impl OpenMlsCrypto,
376 ciphersuite: Ciphersuite,
377 public_key: &EncryptionKey,
378 group_context: &[u8],
379 ) -> Result<HpkeCiphertext, LibraryError> {
380 public_key.encrypt(
381 crypto,
382 ciphersuite,
383 group_context,
384 self.path_secret.as_slice(),
385 )
386 }
387
388 pub(crate) fn secret(self) -> Secret {
390 self.path_secret
391 }
392
393 pub(crate) fn decrypt(
400 crypto: &impl OpenMlsCrypto,
401 ciphersuite: Ciphersuite,
402 ciphertext: &HpkeCiphertext,
403 private_key: &EncryptionPrivateKey,
404 group_context: &[u8],
405 ) -> Result<PathSecret, PathSecretError> {
406 private_key
408 .decrypt(crypto, ciphersuite, ciphertext, group_context)
409 .map(|path_secret| Self { path_secret })
410 .map_err(|e| e.into())
411 }
412}
413
414#[derive(Error, Debug, PartialEq, Clone)]
416pub(crate) enum PathSecretError {
417 #[error(transparent)]
419 DecryptionError(#[from] hpke::Error),
420}
421
422#[derive(Debug, TlsDeserialize, TlsDeserializeBytes, TlsSize)]
433pub(crate) struct GroupSecrets {
434 pub(crate) joiner_secret: JoinerSecret,
435 pub(crate) path_secret: Option<PathSecret>,
436 pub(crate) psks: Vec<PreSharedKeyId>,
437}
438
439#[derive(TlsSerialize, TlsSize)]
440struct EncodedGroupSecrets<'a> {
441 pub(crate) joiner_secret: &'a JoinerSecret,
442 pub(crate) path_secret: Option<&'a PathSecret>,
443 pub(crate) psks: &'a [PreSharedKeyId],
444}
445
446#[derive(Error, Debug, PartialEq, Clone)]
448pub enum GroupSecretsError {
449 #[error("Decryption failed.")]
451 DecryptionFailed,
452 #[error("Malformed.")]
454 Malformed,
455}
456
457impl GroupSecrets {
458 pub(crate) fn try_from_ciphertext(
460 skey: &HpkePrivateKey,
461 ciphertext: &HpkeCiphertext,
462 context: &[u8],
463 ciphersuite: Ciphersuite,
464 crypto: &impl OpenMlsCrypto,
465 ) -> Result<Self, GroupSecretsError> {
466 let group_secrets_plaintext =
467 hpke::decrypt_with_label(skey, "Welcome", context, ciphertext, ciphersuite, crypto)
468 .map_err(|_| GroupSecretsError::DecryptionFailed)?;
469
470 let group_secrets = GroupSecrets::tls_deserialize_exact(group_secrets_plaintext)
472 .map_err(|_| GroupSecretsError::Malformed)?;
473
474 Ok(group_secrets)
475 }
476
477 pub(crate) fn new_encoded<'a>(
479 joiner_secret: &JoinerSecret,
480 path_secret: Option<&'a PathSecret>,
481 psks: &'a [PreSharedKeyId],
482 ) -> Result<Vec<u8>, tls_codec::Error> {
483 EncodedGroupSecrets {
484 joiner_secret,
485 path_secret,
486 psks,
487 }
488 .tls_serialize_detached()
489 }
490}
491
492#[cfg(test)]
493impl GroupSecrets {
494 pub fn random_encoded(
495 ciphersuite: Ciphersuite,
496 rng: &impl OpenMlsRand,
497 ) -> Result<Vec<u8>, tls_codec::Error> {
498 let psk_id = PreSharedKeyId::new(
499 ciphersuite,
500 rng,
501 Psk::External(ExternalPsk::new(
502 rng.random_vec(ciphersuite.hash_length())
503 .expect("Not enough randomness."),
504 )),
505 )
506 .expect("An unexpected error occurred.");
507 let psks = vec![psk_id];
508
509 GroupSecrets::new_encoded(
510 &JoinerSecret::random(ciphersuite, rng),
511 Some(&PathSecret {
512 path_secret: Secret::random(ciphersuite, rng).expect("Not enough randomness."),
513 }),
514 &psks,
515 )
516 }
517}