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(all(test, feature = "generate-kats"))]
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(all(test, feature = "generate-kats"))]
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 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
297#[cfg(any(feature = "test-utils", test))]
300impl From<CommitIn> for Commit {
301 fn from(commit: CommitIn) -> Self {
302 Self {
303 proposals: commit.proposals.into_iter().map(Into::into).collect(),
304 path: commit.path.map(Into::into),
305 }
306 }
307}
308
309impl From<Commit> for CommitIn {
310 fn from(commit: Commit) -> Self {
311 Self {
312 proposals: commit.proposals.into_iter().map(Into::into).collect(),
313 path: commit.path.map(Into::into),
314 }
315 }
316}
317
318#[derive(
321 Debug,
322 PartialEq,
323 Clone,
324 Serialize,
325 Deserialize,
326 TlsDeserialize,
327 TlsDeserializeBytes,
328 TlsSerialize,
329 TlsSize,
330)]
331pub struct ConfirmationTag(pub(crate) Mac);
332
333#[derive(
343 Debug, Serialize, Deserialize, TlsSerialize, TlsDeserialize, TlsDeserializeBytes, TlsSize,
344)]
345#[cfg_attr(any(feature = "test-utils", test), derive(PartialEq, Clone))]
346pub(crate) struct PathSecret {
347 pub(crate) path_secret: Secret,
348}
349
350impl From<Secret> for PathSecret {
351 fn from(path_secret: Secret) -> Self {
352 Self { path_secret }
353 }
354}
355
356impl PathSecret {
357 pub(crate) fn derive_key_pair(
359 &self,
360 crypto: &impl OpenMlsCrypto,
361 ciphersuite: Ciphersuite,
362 ) -> Result<EncryptionKeyPair, LibraryError> {
363 let node_secret = self
364 .path_secret
365 .kdf_expand_label(crypto, ciphersuite, "node", &[], ciphersuite.hash_length())
366 .map_err(LibraryError::unexpected_crypto_error)?;
367 let HpkeKeyPair { public, private } = crypto
368 .derive_hpke_keypair(ciphersuite.hpke_config(), node_secret.as_slice())
369 .map_err(LibraryError::unexpected_crypto_error)?;
370
371 Ok((HpkePublicKey::from(public), private).into())
372 }
373
374 pub(crate) fn derive_path_secret(
376 &self,
377 crypto: &impl OpenMlsCrypto,
378 ciphersuite: Ciphersuite,
379 ) -> Result<Self, LibraryError> {
380 let path_secret = self
381 .path_secret
382 .kdf_expand_label(crypto, ciphersuite, "path", &[], ciphersuite.hash_length())
383 .map_err(LibraryError::unexpected_crypto_error)?;
384 Ok(Self { path_secret })
385 }
386
387 pub(crate) fn encrypt(
390 &self,
391 crypto: &impl OpenMlsCrypto,
392 ciphersuite: Ciphersuite,
393 public_key: &EncryptionKey,
394 group_context: &[u8],
395 ) -> Result<HpkeCiphertext, LibraryError> {
396 public_key.encrypt(
397 crypto,
398 ciphersuite,
399 group_context,
400 self.path_secret.as_slice(),
401 )
402 }
403
404 pub(crate) fn secret(self) -> Secret {
406 self.path_secret
407 }
408
409 pub(crate) fn decrypt(
416 crypto: &impl OpenMlsCrypto,
417 ciphersuite: Ciphersuite,
418 ciphertext: &HpkeCiphertext,
419 private_key: &EncryptionPrivateKey,
420 group_context: &[u8],
421 ) -> Result<PathSecret, PathSecretError> {
422 private_key
424 .decrypt(crypto, ciphersuite, ciphertext, group_context)
425 .map(|path_secret| Self { path_secret })
426 .map_err(|e| e.into())
427 }
428}
429
430#[derive(Error, Debug, PartialEq, Clone)]
432pub(crate) enum PathSecretError {
433 #[error(transparent)]
435 DecryptionError(#[from] hpke::Error),
436}
437
438#[derive(Debug, TlsDeserialize, TlsDeserializeBytes, TlsSize)]
449pub(crate) struct GroupSecrets {
450 pub(crate) joiner_secret: JoinerSecret,
451 pub(crate) path_secret: Option<PathSecret>,
452 pub(crate) psks: Vec<PreSharedKeyId>,
453}
454
455#[derive(TlsSerialize, TlsSize)]
456struct EncodedGroupSecrets<'a> {
457 pub(crate) joiner_secret: &'a JoinerSecret,
458 pub(crate) path_secret: Option<&'a PathSecret>,
459 pub(crate) psks: &'a [PreSharedKeyId],
460}
461
462#[derive(Error, Debug, PartialEq, Clone)]
464pub enum GroupSecretsError {
465 #[error("Decryption failed.")]
467 DecryptionFailed,
468 #[error("Malformed.")]
470 Malformed,
471}
472
473impl GroupSecrets {
474 pub(crate) fn try_from_ciphertext(
476 skey: &HpkePrivateKey,
477 ciphertext: &HpkeCiphertext,
478 context: &[u8],
479 ciphersuite: Ciphersuite,
480 crypto: &impl OpenMlsCrypto,
481 ) -> Result<Self, GroupSecretsError> {
482 let group_secrets_plaintext =
483 hpke::decrypt_with_label(skey, "Welcome", context, ciphertext, ciphersuite, crypto)
484 .map_err(|_| GroupSecretsError::DecryptionFailed)?;
485
486 let group_secrets = GroupSecrets::tls_deserialize_exact(group_secrets_plaintext)
488 .map_err(|_| GroupSecretsError::Malformed)?;
489
490 Ok(group_secrets)
491 }
492
493 pub(crate) fn new_encoded<'a>(
495 joiner_secret: &JoinerSecret,
496 path_secret: Option<&'a PathSecret>,
497 psks: &'a [PreSharedKeyId],
498 ) -> Result<Vec<u8>, tls_codec::Error> {
499 EncodedGroupSecrets {
500 joiner_secret,
501 path_secret,
502 psks,
503 }
504 .tls_serialize_detached()
505 }
506}
507
508#[cfg(all(test, feature = "generate-kats"))]
509impl GroupSecrets {
510 pub fn random_encoded(
511 ciphersuite: Ciphersuite,
512 rng: &impl OpenMlsRand,
513 ) -> Result<Vec<u8>, tls_codec::Error> {
514 let psk_id = PreSharedKeyId::new(
515 ciphersuite,
516 rng,
517 Psk::External(ExternalPsk::new(
518 rng.random_vec(ciphersuite.hash_length())
519 .expect("Not enough randomness."),
520 )),
521 )
522 .expect("An unexpected error occurred.");
523 let psks = vec![psk_id];
524
525 GroupSecrets::new_encoded(
526 &JoinerSecret::random(ciphersuite, rng),
527 Some(&PathSecret {
528 path_secret: Secret::random(ciphersuite, rng).expect("Not enough randomness."),
529 }),
530 &psks,
531 )
532 }
533}