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,
63 Debug,
64 Eq,
65 PartialEq,
66 TlsDeserialize,
67 TlsDeserializeBytes,
68 TlsSerialize,
69 TlsSize,
70 serde::Serialize,
71 serde::Deserialize,
72)]
73pub struct Welcome {
74 cipher_suite: Ciphersuite,
75 secrets: Vec<EncryptedGroupSecrets>,
76 encrypted_group_info: VLBytes,
77}
78
79impl Welcome {
80 pub(crate) fn new(
83 cipher_suite: Ciphersuite,
84 secrets: Vec<EncryptedGroupSecrets>,
85 encrypted_group_info: Vec<u8>,
86 ) -> Self {
87 Self {
88 cipher_suite,
89 secrets,
90 encrypted_group_info: encrypted_group_info.into(),
91 }
92 }
93
94 pub(crate) fn find_encrypted_group_secret(
95 &self,
96 hash_ref: HashReference,
97 ) -> Option<&EncryptedGroupSecrets> {
98 self.secrets()
99 .iter()
100 .find(|egs| hash_ref == egs.new_member())
101 }
102
103 pub(crate) fn ciphersuite(&self) -> Ciphersuite {
105 self.cipher_suite
106 }
107
108 pub fn secrets(&self) -> &[EncryptedGroupSecrets] {
110 self.secrets.as_slice()
111 }
112
113 pub(crate) fn encrypted_group_info(&self) -> &[u8] {
115 self.encrypted_group_info.as_slice()
116 }
117
118 #[cfg(test)]
120 pub fn set_encrypted_group_info(&mut self, encrypted_group_info: Vec<u8>) {
121 self.encrypted_group_info = encrypted_group_info.into();
122 }
123}
124
125#[derive(
129 Clone,
130 Debug,
131 Eq,
132 PartialEq,
133 TlsDeserialize,
134 TlsDeserializeBytes,
135 TlsSerialize,
136 TlsSize,
137 serde::Serialize,
138 serde::Deserialize,
139)]
140pub struct EncryptedGroupSecrets {
141 new_member: KeyPackageRef,
143 encrypted_group_secrets: HpkeCiphertext,
145}
146
147impl EncryptedGroupSecrets {
148 pub fn new(new_member: KeyPackageRef, encrypted_group_secrets: HpkeCiphertext) -> Self {
150 Self {
151 new_member,
152 encrypted_group_secrets,
153 }
154 }
155
156 pub fn new_member(&self) -> KeyPackageRef {
158 self.new_member.clone()
159 }
160
161 pub(crate) fn encrypted_group_secrets(&self) -> &HpkeCiphertext {
163 &self.encrypted_group_secrets
164 }
165}
166
167#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, TlsSerialize, TlsSize)]
186pub(crate) struct Commit {
187 pub(crate) proposals: Vec<ProposalOrRef>,
188 pub(crate) path: Option<UpdatePath>,
189}
190
191impl Commit {
192 #[cfg(test)]
194 pub fn has_path(&self) -> bool {
195 self.path.is_some()
196 }
197
198 #[cfg(test)]
200 pub(crate) fn path(&self) -> &Option<UpdatePath> {
201 &self.path
202 }
203}
204
205#[derive(
206 Debug,
207 PartialEq,
208 Clone,
209 Serialize,
210 Deserialize,
211 TlsDeserialize,
212 TlsDeserializeBytes,
213 TlsSerialize,
214 TlsSize,
215)]
216pub(crate) struct CommitIn {
217 proposals: Vec<ProposalOrRefIn>,
218 path: Option<UpdatePathIn>,
219}
220
221impl CommitIn {
222 pub(crate) fn unverified_credential(&self) -> Option<CredentialWithKey> {
223 self.path.as_ref().map(|p| {
224 let credential = p.leaf_node().credential().clone();
225 let pk = p.leaf_node().signature_key().clone();
226 CredentialWithKey {
227 credential,
228 signature_key: pk,
229 }
230 })
231 }
232
233 pub(crate) fn validate(
235 self,
236 ciphersuite: Ciphersuite,
237 crypto: &impl OpenMlsCrypto,
238 sender_context: SenderContext,
239 protocol_version: ProtocolVersion,
240 ) -> Result<Commit, ValidationError> {
241 let proposals = self
242 .proposals
243 .into_iter()
244 .map(|p| p.validate(crypto, ciphersuite, protocol_version))
245 .collect::<Result<Vec<_>, _>>()?;
246
247 let path = if let Some(path) = self.path {
248 let tree_position = match sender_context {
249 SenderContext::Member((group_id, leaf_index)) => {
250 TreePosition::new(group_id, leaf_index)
251 }
252 SenderContext::ExternalCommit {
253 group_id,
254 leftmost_blank_index,
255 self_removes_in_store,
256 } => {
257 let former_sender_index = proposals.iter().find_map(|p| {
260 p.as_proposal()
261 .and_then(|p| p.as_remove())
262 .map(|r| r.removed())
263 });
264
265 let self_removed_indices =
268 self_removes_in_store.into_iter().filter_map(|self_remove| {
269 proposals.iter().find_map(|committed_p| {
270 committed_p.as_reference().and_then(|committed_p_ref| {
271 (&self_remove.proposal_ref == committed_p_ref)
272 .then_some(self_remove.sender)
273 })
274 })
275 });
276
277 let new_leaf_index = [leftmost_blank_index]
278 .into_iter()
279 .chain(former_sender_index)
280 .chain(self_removed_indices)
281 .min()
282 .ok_or(ValidationError::LibraryError(LibraryError::custom(
283 "The iterator should have at least one element.",
284 )))?;
285
286 TreePosition::new(group_id, new_leaf_index)
287 }
288 };
289 Some(path.into_verified(ciphersuite, crypto, tree_position)?)
290 } else {
291 None
292 };
293 Ok(Commit { proposals, path })
294 }
295
296 #[cfg(feature = "extensions-draft-08")]
297 pub(crate) fn proposals(&self) -> &[ProposalOrRefIn] {
298 &self.proposals
299 }
300}
301
302#[cfg(any(feature = "test-utils", test))]
305impl From<CommitIn> for Commit {
306 fn from(commit: CommitIn) -> Self {
307 Self {
308 proposals: commit.proposals.into_iter().map(Into::into).collect(),
309 path: commit.path.map(Into::into),
310 }
311 }
312}
313
314impl From<Commit> for CommitIn {
315 fn from(commit: Commit) -> Self {
316 Self {
317 proposals: commit.proposals.into_iter().map(Into::into).collect(),
318 path: commit.path.map(Into::into),
319 }
320 }
321}
322
323#[derive(
326 Debug,
327 PartialEq,
328 Clone,
329 Serialize,
330 Deserialize,
331 TlsDeserialize,
332 TlsDeserializeBytes,
333 TlsSerialize,
334 TlsSize,
335)]
336pub struct ConfirmationTag(pub(crate) Mac);
337
338#[derive(
348 Debug, Serialize, Deserialize, TlsSerialize, TlsDeserialize, TlsDeserializeBytes, TlsSize,
349)]
350#[cfg_attr(any(feature = "test-utils", test), derive(PartialEq, Clone))]
351pub(crate) struct PathSecret {
352 pub(crate) path_secret: Secret,
353}
354
355impl From<Secret> for PathSecret {
356 fn from(path_secret: Secret) -> Self {
357 Self { path_secret }
358 }
359}
360
361impl PathSecret {
362 pub(crate) fn derive_key_pair(
364 &self,
365 crypto: &impl OpenMlsCrypto,
366 ciphersuite: Ciphersuite,
367 ) -> Result<EncryptionKeyPair, LibraryError> {
368 let node_secret = self
369 .path_secret
370 .kdf_expand_label(crypto, ciphersuite, "node", &[], ciphersuite.hash_length())
371 .map_err(LibraryError::unexpected_crypto_error)?;
372 let HpkeKeyPair { public, private } = crypto
373 .derive_hpke_keypair(ciphersuite.hpke_config(), node_secret.as_slice())
374 .map_err(LibraryError::unexpected_crypto_error)?;
375
376 Ok((HpkePublicKey::from(public), private).into())
377 }
378
379 pub(crate) fn derive_path_secret(
381 &self,
382 crypto: &impl OpenMlsCrypto,
383 ciphersuite: Ciphersuite,
384 ) -> Result<Self, LibraryError> {
385 let path_secret = self
386 .path_secret
387 .kdf_expand_label(crypto, ciphersuite, "path", &[], ciphersuite.hash_length())
388 .map_err(LibraryError::unexpected_crypto_error)?;
389 Ok(Self { path_secret })
390 }
391
392 pub(crate) fn encrypt(
395 &self,
396 crypto: &impl OpenMlsCrypto,
397 ciphersuite: Ciphersuite,
398 public_key: &EncryptionKey,
399 group_context: &[u8],
400 ) -> Result<HpkeCiphertext, LibraryError> {
401 public_key.encrypt(
402 crypto,
403 ciphersuite,
404 group_context,
405 self.path_secret.as_slice(),
406 )
407 }
408
409 pub(crate) fn secret(self) -> Secret {
411 self.path_secret
412 }
413
414 pub(crate) fn decrypt(
421 crypto: &impl OpenMlsCrypto,
422 ciphersuite: Ciphersuite,
423 ciphertext: &HpkeCiphertext,
424 private_key: &EncryptionPrivateKey,
425 group_context: &[u8],
426 ) -> Result<PathSecret, PathSecretError> {
427 private_key
429 .decrypt(crypto, ciphersuite, ciphertext, group_context)
430 .map(|path_secret| Self { path_secret })
431 .map_err(|e| e.into())
432 }
433}
434
435#[derive(Error, Debug, PartialEq, Clone)]
437pub(crate) enum PathSecretError {
438 #[error(transparent)]
440 DecryptionError(#[from] hpke::Error),
441}
442
443#[derive(Debug, TlsDeserialize, TlsDeserializeBytes, TlsSize)]
454pub(crate) struct GroupSecrets {
455 pub(crate) joiner_secret: JoinerSecret,
456 pub(crate) path_secret: Option<PathSecret>,
457 pub(crate) psks: Vec<PreSharedKeyId>,
458}
459
460#[derive(TlsSerialize, TlsSize)]
461struct EncodedGroupSecrets<'a> {
462 pub(crate) joiner_secret: &'a JoinerSecret,
463 pub(crate) path_secret: Option<&'a PathSecret>,
464 pub(crate) psks: &'a [PreSharedKeyId],
465}
466
467#[derive(Error, Debug, PartialEq, Clone)]
469pub enum GroupSecretsError {
470 #[error("Decryption failed.")]
472 DecryptionFailed,
473 #[error("Malformed.")]
475 Malformed,
476}
477
478impl GroupSecrets {
479 pub(crate) fn try_from_ciphertext(
481 skey: &HpkePrivateKey,
482 ciphertext: &HpkeCiphertext,
483 context: &[u8],
484 ciphersuite: Ciphersuite,
485 crypto: &impl OpenMlsCrypto,
486 ) -> Result<Self, GroupSecretsError> {
487 let group_secrets_plaintext =
488 hpke::decrypt_with_label(skey, "Welcome", context, ciphertext, ciphersuite, crypto)
489 .map_err(|_| GroupSecretsError::DecryptionFailed)?;
490
491 let group_secrets = GroupSecrets::tls_deserialize_exact(group_secrets_plaintext)
493 .map_err(|_| GroupSecretsError::Malformed)?;
494
495 Ok(group_secrets)
496 }
497
498 pub(crate) fn new_encoded<'a>(
500 joiner_secret: &JoinerSecret,
501 path_secret: Option<&'a PathSecret>,
502 psks: &'a [PreSharedKeyId],
503 ) -> Result<Vec<u8>, tls_codec::Error> {
504 EncodedGroupSecrets {
505 joiner_secret,
506 path_secret,
507 psks,
508 }
509 .tls_serialize_detached()
510 }
511}
512
513#[cfg(test)]
514impl GroupSecrets {
515 pub fn random_encoded(
516 ciphersuite: Ciphersuite,
517 rng: &impl OpenMlsRand,
518 ) -> Result<Vec<u8>, tls_codec::Error> {
519 let psk_id = PreSharedKeyId::new(
520 ciphersuite,
521 rng,
522 Psk::External(ExternalPsk::new(
523 rng.random_vec(ciphersuite.hash_length())
524 .expect("Not enough randomness."),
525 )),
526 )
527 .expect("An unexpected error occurred.");
528 let psks = vec![psk_id];
529
530 GroupSecrets::new_encoded(
531 &JoinerSecret::random(ciphersuite, rng),
532 Some(&PathSecret {
533 path_secret: Secret::random(ciphersuite, rng).expect("Not enough randomness."),
534 }),
535 &psks,
536 )
537 }
538}