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 p.as_proposal()
243 .and_then(|p| p.as_remove())
244 .map(|r| r.removed())
245 });
246
247 let self_removed_indices =
250 self_removes_in_store.into_iter().filter_map(|self_remove| {
251 proposals.iter().find_map(|committed_p| {
252 committed_p.as_reference().and_then(|committed_p_ref| {
253 (&self_remove.proposal_ref == committed_p_ref)
254 .then_some(self_remove.sender)
255 })
256 })
257 });
258
259 let new_leaf_index = [leftmost_blank_index]
260 .into_iter()
261 .chain(former_sender_index)
262 .chain(self_removed_indices)
263 .min()
264 .ok_or(ValidationError::LibraryError(LibraryError::custom(
265 "The iterator should have at least one element.",
266 )))?;
267
268 TreePosition::new(group_id, new_leaf_index)
269 }
270 };
271 Some(path.into_verified(ciphersuite, crypto, tree_position)?)
272 } else {
273 None
274 };
275 Ok(Commit { proposals, path })
276 }
277}
278
279#[cfg(any(feature = "test-utils", test))]
282impl From<CommitIn> for Commit {
283 fn from(commit: CommitIn) -> Self {
284 Self {
285 proposals: commit.proposals.into_iter().map(Into::into).collect(),
286 path: commit.path.map(Into::into),
287 }
288 }
289}
290
291impl From<Commit> for CommitIn {
292 fn from(commit: Commit) -> Self {
293 Self {
294 proposals: commit.proposals.into_iter().map(Into::into).collect(),
295 path: commit.path.map(Into::into),
296 }
297 }
298}
299
300#[derive(
303 Debug,
304 PartialEq,
305 Clone,
306 Serialize,
307 Deserialize,
308 TlsDeserialize,
309 TlsDeserializeBytes,
310 TlsSerialize,
311 TlsSize,
312)]
313pub struct ConfirmationTag(pub(crate) Mac);
314
315#[derive(
325 Debug, Serialize, Deserialize, TlsSerialize, TlsDeserialize, TlsDeserializeBytes, TlsSize,
326)]
327#[cfg_attr(any(feature = "test-utils", test), derive(PartialEq, Clone))]
328pub(crate) struct PathSecret {
329 pub(crate) path_secret: Secret,
330}
331
332impl From<Secret> for PathSecret {
333 fn from(path_secret: Secret) -> Self {
334 Self { path_secret }
335 }
336}
337
338impl PathSecret {
339 pub(crate) fn derive_key_pair(
341 &self,
342 crypto: &impl OpenMlsCrypto,
343 ciphersuite: Ciphersuite,
344 ) -> Result<EncryptionKeyPair, LibraryError> {
345 let node_secret = self
346 .path_secret
347 .kdf_expand_label(crypto, ciphersuite, "node", &[], ciphersuite.hash_length())
348 .map_err(LibraryError::unexpected_crypto_error)?;
349 let HpkeKeyPair { public, private } = crypto
350 .derive_hpke_keypair(ciphersuite.hpke_config(), node_secret.as_slice())
351 .map_err(LibraryError::unexpected_crypto_error)?;
352
353 Ok((HpkePublicKey::from(public), private).into())
354 }
355
356 pub(crate) fn derive_path_secret(
358 &self,
359 crypto: &impl OpenMlsCrypto,
360 ciphersuite: Ciphersuite,
361 ) -> Result<Self, LibraryError> {
362 let path_secret = self
363 .path_secret
364 .kdf_expand_label(crypto, ciphersuite, "path", &[], ciphersuite.hash_length())
365 .map_err(LibraryError::unexpected_crypto_error)?;
366 Ok(Self { path_secret })
367 }
368
369 pub(crate) fn encrypt(
372 &self,
373 crypto: &impl OpenMlsCrypto,
374 ciphersuite: Ciphersuite,
375 public_key: &EncryptionKey,
376 group_context: &[u8],
377 ) -> Result<HpkeCiphertext, LibraryError> {
378 public_key.encrypt(
379 crypto,
380 ciphersuite,
381 group_context,
382 self.path_secret.as_slice(),
383 )
384 }
385
386 pub(crate) fn secret(self) -> Secret {
388 self.path_secret
389 }
390
391 pub(crate) fn decrypt(
398 crypto: &impl OpenMlsCrypto,
399 ciphersuite: Ciphersuite,
400 ciphertext: &HpkeCiphertext,
401 private_key: &EncryptionPrivateKey,
402 group_context: &[u8],
403 ) -> Result<PathSecret, PathSecretError> {
404 private_key
406 .decrypt(crypto, ciphersuite, ciphertext, group_context)
407 .map(|path_secret| Self { path_secret })
408 .map_err(|e| e.into())
409 }
410}
411
412#[derive(Error, Debug, PartialEq, Clone)]
414pub(crate) enum PathSecretError {
415 #[error(transparent)]
417 DecryptionError(#[from] hpke::Error),
418}
419
420#[derive(Debug, TlsDeserialize, TlsDeserializeBytes, TlsSize)]
431pub(crate) struct GroupSecrets {
432 pub(crate) joiner_secret: JoinerSecret,
433 pub(crate) path_secret: Option<PathSecret>,
434 pub(crate) psks: Vec<PreSharedKeyId>,
435}
436
437#[derive(TlsSerialize, TlsSize)]
438struct EncodedGroupSecrets<'a> {
439 pub(crate) joiner_secret: &'a JoinerSecret,
440 pub(crate) path_secret: Option<&'a PathSecret>,
441 pub(crate) psks: &'a [PreSharedKeyId],
442}
443
444#[derive(Error, Debug, PartialEq, Clone)]
446pub enum GroupSecretsError {
447 #[error("Decryption failed.")]
449 DecryptionFailed,
450 #[error("Malformed.")]
452 Malformed,
453}
454
455impl GroupSecrets {
456 pub(crate) fn try_from_ciphertext(
458 skey: &HpkePrivateKey,
459 ciphertext: &HpkeCiphertext,
460 context: &[u8],
461 ciphersuite: Ciphersuite,
462 crypto: &impl OpenMlsCrypto,
463 ) -> Result<Self, GroupSecretsError> {
464 let group_secrets_plaintext =
465 hpke::decrypt_with_label(skey, "Welcome", context, ciphertext, ciphersuite, crypto)
466 .map_err(|_| GroupSecretsError::DecryptionFailed)?;
467
468 let group_secrets = GroupSecrets::tls_deserialize_exact(group_secrets_plaintext)
470 .map_err(|_| GroupSecretsError::Malformed)?;
471
472 Ok(group_secrets)
473 }
474
475 pub(crate) fn new_encoded<'a>(
477 joiner_secret: &JoinerSecret,
478 path_secret: Option<&'a PathSecret>,
479 psks: &'a [PreSharedKeyId],
480 ) -> Result<Vec<u8>, tls_codec::Error> {
481 EncodedGroupSecrets {
482 joiner_secret,
483 path_secret,
484 psks,
485 }
486 .tls_serialize_detached()
487 }
488}
489
490#[cfg(test)]
491impl GroupSecrets {
492 pub fn random_encoded(
493 ciphersuite: Ciphersuite,
494 rng: &impl OpenMlsRand,
495 ) -> Result<Vec<u8>, tls_codec::Error> {
496 let psk_id = PreSharedKeyId::new(
497 ciphersuite,
498 rng,
499 Psk::External(ExternalPsk::new(
500 rng.random_vec(ciphersuite.hash_length())
501 .expect("Not enough randomness."),
502 )),
503 )
504 .expect("An unexpected error occurred.");
505 let psks = vec![psk_id];
506
507 GroupSecrets::new_encoded(
508 &JoinerSecret::random(ciphersuite, rng),
509 Some(&PathSecret {
510 path_secret: Secret::random(ciphersuite, rng).expect("Not enough randomness."),
511 }),
512 &psks,
513 )
514 }
515}