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((group_id, free_leaf_index)) => {
235 let new_leaf_index = proposals
238 .iter()
239 .find_map(|p| match p {
240 ProposalOrRef::Proposal(p) => match p {
241 Proposal::Remove(r) => Some(r.removed()),
242 _ => None,
243 },
244 ProposalOrRef::Reference(_) => None,
245 })
246 .map(|removed_index| {
248 if removed_index < free_leaf_index {
249 removed_index
250 } else {
251 free_leaf_index
252 }
253 })
254 .unwrap_or(free_leaf_index);
256
257 TreePosition::new(group_id, new_leaf_index)
258 }
259 };
260 Some(path.into_verified(ciphersuite, crypto, tree_position)?)
261 } else {
262 None
263 };
264 Ok(Commit { proposals, path })
265 }
266}
267
268#[cfg(any(feature = "test-utils", test))]
271impl From<CommitIn> for Commit {
272 fn from(commit: CommitIn) -> Self {
273 Self {
274 proposals: commit.proposals.into_iter().map(Into::into).collect(),
275 path: commit.path.map(Into::into),
276 }
277 }
278}
279
280impl From<Commit> for CommitIn {
281 fn from(commit: Commit) -> Self {
282 Self {
283 proposals: commit.proposals.into_iter().map(Into::into).collect(),
284 path: commit.path.map(Into::into),
285 }
286 }
287}
288
289#[derive(
292 Debug,
293 PartialEq,
294 Clone,
295 Serialize,
296 Deserialize,
297 TlsDeserialize,
298 TlsDeserializeBytes,
299 TlsSerialize,
300 TlsSize,
301)]
302pub struct ConfirmationTag(pub(crate) Mac);
303
304#[derive(
314 Debug, Serialize, Deserialize, TlsSerialize, TlsDeserialize, TlsDeserializeBytes, TlsSize,
315)]
316#[cfg_attr(any(feature = "test-utils", test), derive(PartialEq, Clone))]
317pub(crate) struct PathSecret {
318 pub(crate) path_secret: Secret,
319}
320
321impl From<Secret> for PathSecret {
322 fn from(path_secret: Secret) -> Self {
323 Self { path_secret }
324 }
325}
326
327impl PathSecret {
328 pub(crate) fn derive_key_pair(
330 &self,
331 crypto: &impl OpenMlsCrypto,
332 ciphersuite: Ciphersuite,
333 ) -> Result<EncryptionKeyPair, LibraryError> {
334 let node_secret = self
335 .path_secret
336 .kdf_expand_label(crypto, ciphersuite, "node", &[], ciphersuite.hash_length())
337 .map_err(LibraryError::unexpected_crypto_error)?;
338 let HpkeKeyPair { public, private } = crypto
339 .derive_hpke_keypair(ciphersuite.hpke_config(), node_secret.as_slice())
340 .map_err(LibraryError::unexpected_crypto_error)?;
341
342 Ok((HpkePublicKey::from(public), private).into())
343 }
344
345 pub(crate) fn derive_path_secret(
347 &self,
348 crypto: &impl OpenMlsCrypto,
349 ciphersuite: Ciphersuite,
350 ) -> Result<Self, LibraryError> {
351 let path_secret = self
352 .path_secret
353 .kdf_expand_label(crypto, ciphersuite, "path", &[], ciphersuite.hash_length())
354 .map_err(LibraryError::unexpected_crypto_error)?;
355 Ok(Self { path_secret })
356 }
357
358 pub(crate) fn encrypt(
361 &self,
362 crypto: &impl OpenMlsCrypto,
363 ciphersuite: Ciphersuite,
364 public_key: &EncryptionKey,
365 group_context: &[u8],
366 ) -> Result<HpkeCiphertext, LibraryError> {
367 public_key.encrypt(
368 crypto,
369 ciphersuite,
370 group_context,
371 self.path_secret.as_slice(),
372 )
373 }
374
375 pub(crate) fn secret(self) -> Secret {
377 self.path_secret
378 }
379
380 pub(crate) fn decrypt(
387 crypto: &impl OpenMlsCrypto,
388 ciphersuite: Ciphersuite,
389 ciphertext: &HpkeCiphertext,
390 private_key: &EncryptionPrivateKey,
391 group_context: &[u8],
392 ) -> Result<PathSecret, PathSecretError> {
393 private_key
395 .decrypt(crypto, ciphersuite, ciphertext, group_context)
396 .map(|path_secret| Self { path_secret })
397 .map_err(|e| e.into())
398 }
399}
400
401#[derive(Error, Debug, PartialEq, Clone)]
403pub(crate) enum PathSecretError {
404 #[error(transparent)]
406 DecryptionError(#[from] hpke::Error),
407}
408
409#[derive(Debug, TlsDeserialize, TlsDeserializeBytes, TlsSize)]
420pub(crate) struct GroupSecrets {
421 pub(crate) joiner_secret: JoinerSecret,
422 pub(crate) path_secret: Option<PathSecret>,
423 pub(crate) psks: Vec<PreSharedKeyId>,
424}
425
426#[derive(TlsSerialize, TlsSize)]
427struct EncodedGroupSecrets<'a> {
428 pub(crate) joiner_secret: &'a JoinerSecret,
429 pub(crate) path_secret: Option<&'a PathSecret>,
430 pub(crate) psks: &'a [PreSharedKeyId],
431}
432
433#[derive(Error, Debug, PartialEq, Clone)]
435pub enum GroupSecretsError {
436 #[error("Decryption failed.")]
438 DecryptionFailed,
439 #[error("Malformed.")]
441 Malformed,
442}
443
444impl GroupSecrets {
445 pub(crate) fn try_from_ciphertext(
447 skey: &HpkePrivateKey,
448 ciphertext: &HpkeCiphertext,
449 context: &[u8],
450 ciphersuite: Ciphersuite,
451 crypto: &impl OpenMlsCrypto,
452 ) -> Result<Self, GroupSecretsError> {
453 let group_secrets_plaintext =
454 hpke::decrypt_with_label(skey, "Welcome", context, ciphertext, ciphersuite, crypto)
455 .map_err(|_| GroupSecretsError::DecryptionFailed)?;
456
457 let group_secrets = GroupSecrets::tls_deserialize_exact(group_secrets_plaintext)
459 .map_err(|_| GroupSecretsError::Malformed)?;
460
461 Ok(group_secrets)
462 }
463
464 pub(crate) fn new_encoded<'a>(
466 joiner_secret: &JoinerSecret,
467 path_secret: Option<&'a PathSecret>,
468 psks: &'a [PreSharedKeyId],
469 ) -> Result<Vec<u8>, tls_codec::Error> {
470 EncodedGroupSecrets {
471 joiner_secret,
472 path_secret,
473 psks,
474 }
475 .tls_serialize_detached()
476 }
477}
478
479#[cfg(test)]
480impl GroupSecrets {
481 pub fn random_encoded(
482 ciphersuite: Ciphersuite,
483 rng: &impl OpenMlsRand,
484 ) -> Result<Vec<u8>, tls_codec::Error> {
485 let psk_id = PreSharedKeyId::new(
486 ciphersuite,
487 rng,
488 Psk::External(ExternalPsk::new(
489 rng.random_vec(ciphersuite.hash_length())
490 .expect("Not enough randomness."),
491 )),
492 )
493 .expect("An unexpected error occurred.");
494 let psks = vec![psk_id];
495
496 GroupSecrets::new_encoded(
497 &JoinerSecret::random(ciphersuite, rng),
498 Some(&PathSecret {
499 path_secret: Secret::random(ciphersuite, rng).expect("Not enough randomness."),
500 }),
501 &psks,
502 )
503 }
504}