1use crate::{
6 ciphersuite::{hash_ref::ProposalRef, signable::Verifiable},
7 credentials::CredentialWithKey,
8 extensions::{AnyObject, Extensions},
9 framing::SenderContext,
10 group::errors::ValidationError,
11 key_packages::*,
12 prelude::InvalidExtensionError,
13 treesync::node::leaf_node::{LeafNodeIn, TreePosition, VerifiableLeafNode},
14 versions::ProtocolVersion,
15};
16
17use openmls_traits::{crypto::OpenMlsCrypto, types::Ciphersuite};
18use serde::{Deserialize, Serialize};
19use tls_codec::{TlsDeserialize, TlsDeserializeBytes, TlsSerialize, TlsSize};
20
21use super::{
22 proposals::{
23 AddProposal, ExternalInitProposal, GroupContextExtensionProposal, PreSharedKeyProposal,
24 Proposal, ProposalOrRef, ProposalType, ReInitProposal, RemoveProposal, UpdateProposal,
25 },
26 CustomProposal,
27};
28
29#[cfg(feature = "extensions-draft-08")]
30use super::proposals::{AppDataUpdateProposal, AppEphemeralProposal};
31
32#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
52#[allow(missing_docs)]
53#[repr(u16)]
54pub enum ProposalIn {
55 Add(Box<AddProposalIn>),
56 Update(Box<UpdateProposalIn>),
57 Remove(Box<RemoveProposal>),
58 PreSharedKey(Box<PreSharedKeyProposal>),
59 ReInit(Box<ReInitProposal>),
60 ExternalInit(Box<ExternalInitProposal>),
61 GroupContextExtensions(Box<GroupContextExtensionProposalIn>),
62 #[cfg(feature = "extensions-draft-08")]
64 AppDataUpdate(Box<AppDataUpdateProposal>),
65
66 SelfRemove,
68 #[cfg(feature = "extensions-draft-08")]
69 AppEphemeral(Box<AppEphemeralProposal>),
70 Custom(Box<CustomProposal>),
71}
72
73impl ProposalIn {
74 pub fn proposal_type(&self) -> ProposalType {
76 match self {
77 ProposalIn::Add(_) => ProposalType::Add,
78 ProposalIn::Update(_) => ProposalType::Update,
79 ProposalIn::Remove(_) => ProposalType::Remove,
80 ProposalIn::PreSharedKey(_) => ProposalType::PreSharedKey,
81 ProposalIn::ReInit(_) => ProposalType::Reinit,
82 ProposalIn::ExternalInit(_) => ProposalType::ExternalInit,
83 ProposalIn::GroupContextExtensions(_) => ProposalType::GroupContextExtensions,
84 #[cfg(feature = "extensions-draft-08")]
85 ProposalIn::AppDataUpdate(_) => ProposalType::AppDataUpdate,
86 ProposalIn::SelfRemove => ProposalType::SelfRemove,
87 #[cfg(feature = "extensions-draft-08")]
88 ProposalIn::AppEphemeral(_) => ProposalType::AppEphemeral,
89 ProposalIn::Custom(custom_proposal) => {
90 ProposalType::Custom(custom_proposal.proposal_type())
91 }
92 }
93 }
94
95 pub fn is_path_required(&self) -> bool {
97 self.proposal_type().is_path_required()
98 }
99
100 pub(crate) fn validate(
102 self,
103 crypto: &impl OpenMlsCrypto,
104 ciphersuite: Ciphersuite,
105 sender_context: Option<SenderContext>,
106 protocol_version: ProtocolVersion,
107 ) -> Result<Proposal, ValidationError> {
108 Ok(match self {
109 ProposalIn::Add(add) => Proposal::Add(Box::new(add.validate(
110 crypto,
111 protocol_version,
112 ciphersuite,
113 )?)),
114 ProposalIn::Update(update) => {
115 let sender_context =
116 sender_context.ok_or(ValidationError::CommitterIncludedOwnUpdate)?;
117 Proposal::Update(Box::new(update.validate(
118 crypto,
119 ciphersuite,
120 sender_context,
121 )?))
122 }
123 ProposalIn::Remove(remove) => Proposal::Remove(remove),
124 ProposalIn::PreSharedKey(psk) => Proposal::PreSharedKey(psk),
125 ProposalIn::ReInit(reinit) => Proposal::ReInit(reinit),
126 ProposalIn::ExternalInit(external_init) => Proposal::ExternalInit(external_init),
127 ProposalIn::GroupContextExtensions(group_context_extension) => {
128 Proposal::group_context_extensions(group_context_extension.validate()?)
129 }
130 #[cfg(feature = "extensions-draft-08")]
131 ProposalIn::AppDataUpdate(app_data_update) => Proposal::AppDataUpdate(app_data_update),
132 ProposalIn::SelfRemove => Proposal::SelfRemove,
133 #[cfg(feature = "extensions-draft-08")]
134 ProposalIn::AppEphemeral(app_ephemeral) => Proposal::AppEphemeral(app_ephemeral),
135 ProposalIn::Custom(custom) => Proposal::Custom(custom),
136 })
137 }
138}
139
140#[derive(
151 Debug,
152 PartialEq,
153 Clone,
154 Serialize,
155 Deserialize,
156 TlsSerialize,
157 TlsDeserialize,
158 TlsDeserializeBytes,
159 TlsSize,
160)]
161pub struct AddProposalIn {
162 key_package: KeyPackageIn,
163}
164
165impl AddProposalIn {
166 pub(crate) fn unverified_credential(&self) -> CredentialWithKey {
167 self.key_package.unverified_credential()
168 }
169
170 pub(crate) fn validate(
172 self,
173 crypto: &impl OpenMlsCrypto,
174 protocol_version: ProtocolVersion,
175 ciphersuite: Ciphersuite,
176 ) -> Result<AddProposal, ValidationError> {
177 let key_package = self.key_package.validate(crypto, protocol_version)?;
178 if key_package.ciphersuite() != ciphersuite {
180 return Err(ValidationError::InvalidAddProposalCiphersuite);
181 }
182 Ok(AddProposal { key_package })
183 }
184}
185
186#[derive(
198 Debug,
199 PartialEq,
200 Eq,
201 Clone,
202 Serialize,
203 Deserialize,
204 TlsDeserialize,
205 TlsDeserializeBytes,
206 TlsSerialize,
207 TlsSize,
208)]
209pub struct UpdateProposalIn {
210 leaf_node: LeafNodeIn,
211}
212
213impl UpdateProposalIn {
214 pub(crate) fn validate(
216 self,
217 crypto: &impl OpenMlsCrypto,
218 ciphersuite: Ciphersuite,
219 sender_context: SenderContext,
220 ) -> Result<UpdateProposal, ValidationError> {
221 let leaf_node = match self.leaf_node.into_verifiable_leaf_node() {
222 VerifiableLeafNode::Update(mut leaf_node) => {
223 let tree_position = match sender_context {
224 SenderContext::Member((group_id, leaf_index)) => {
225 TreePosition::new(group_id, leaf_index)
226 }
227 _ => return Err(ValidationError::InvalidSenderType),
228 };
229 leaf_node.add_tree_position(tree_position);
230 let pk = &leaf_node
231 .signature_key()
232 .clone()
233 .into_signature_public_key_enriched(ciphersuite.signature_algorithm());
234
235 leaf_node
236 .verify(crypto, pk)
237 .map_err(|_| ValidationError::InvalidLeafNodeSignature)?
238 }
239 _ => return Err(ValidationError::InvalidLeafNodeSourceType),
240 };
241
242 Ok(UpdateProposal { leaf_node })
243 }
244}
245
246#[derive(
250 Debug,
251 PartialEq,
252 Clone,
253 Serialize,
254 Deserialize,
255 TlsSerialize,
256 TlsDeserialize,
257 TlsDeserializeBytes,
258 TlsSize,
259)]
260#[repr(u8)]
261#[allow(missing_docs)]
262pub enum ProposalOrRefIn {
263 #[tls_codec(discriminant = 1)]
264 Proposal(Box<ProposalIn>),
265 Reference(Box<ProposalRef>),
266}
267
268impl ProposalOrRefIn {
269 pub fn validate(
271 self,
272 crypto: &impl OpenMlsCrypto,
273 ciphersuite: Ciphersuite,
274 protocol_version: ProtocolVersion,
275 ) -> Result<ProposalOrRef, ValidationError> {
276 Ok(match self {
277 ProposalOrRefIn::Proposal(proposal_in) => ProposalOrRef::Proposal(Box::new(
278 proposal_in.validate(crypto, ciphersuite, None, protocol_version)?,
279 )),
280 ProposalOrRefIn::Reference(reference) => ProposalOrRef::Reference(reference),
281 })
282 }
283}
284
285#[cfg(any(feature = "test-utils", test))]
288impl From<AddProposalIn> for AddProposal {
289 fn from(value: AddProposalIn) -> Self {
290 Self {
291 key_package: value.key_package.into(),
292 }
293 }
294}
295
296#[cfg(any(feature = "test-utils", test))]
297impl From<AddProposalIn> for Box<AddProposal> {
298 fn from(value: AddProposalIn) -> Self {
299 Box::new(AddProposal {
300 key_package: value.key_package.into(),
301 })
302 }
303}
304
305impl From<AddProposal> for Box<AddProposalIn> {
306 fn from(value: AddProposal) -> Self {
307 Box::new(AddProposalIn {
308 key_package: value.key_package.into(),
309 })
310 }
311}
312
313#[cfg(any(feature = "test-utils", test))]
316impl From<UpdateProposalIn> for UpdateProposal {
317 fn from(value: UpdateProposalIn) -> Self {
318 Self {
319 leaf_node: value.leaf_node.into(),
320 }
321 }
322}
323
324impl From<UpdateProposal> for UpdateProposalIn {
325 fn from(value: UpdateProposal) -> Self {
326 Self {
327 leaf_node: value.leaf_node.into(),
328 }
329 }
330}
331
332#[cfg(any(feature = "test-utils", test))]
335impl From<GroupContextExtensionProposalIn> for GroupContextExtensionProposal {
336 fn from(value: GroupContextExtensionProposalIn) -> Self {
337 Self::new(value.extensions_tbv.try_into().unwrap())
338 }
339}
340
341#[cfg(any(feature = "test-utils", test))]
342impl From<GroupContextExtensionProposalIn> for Box<GroupContextExtensionProposal> {
343 fn from(value: GroupContextExtensionProposalIn) -> Self {
344 Box::new(GroupContextExtensionProposal::new(
345 value.extensions_tbv.try_into().unwrap(),
346 ))
347 }
348}
349
350impl From<GroupContextExtensionProposal> for GroupContextExtensionProposalIn {
351 fn from(value: crate::messages::proposals::GroupContextExtensionProposal) -> Self {
352 Self {
353 extensions_tbv: value.extensions().clone().into(),
354 }
355 }
356}
357
358impl From<GroupContextExtensionProposal> for Box<GroupContextExtensionProposalIn> {
359 fn from(value: GroupContextExtensionProposal) -> Self {
360 Box::new(GroupContextExtensionProposalIn {
361 extensions_tbv: value.into_extensions().into(),
362 })
363 }
364}
365
366#[cfg(any(feature = "test-utils", test))]
367impl From<UpdateProposalIn> for Box<UpdateProposal> {
368 fn from(value: UpdateProposalIn) -> Self {
369 Box::new(UpdateProposal {
370 leaf_node: value.leaf_node.into(),
371 })
372 }
373}
374
375impl From<UpdateProposal> for Box<UpdateProposalIn> {
376 fn from(value: UpdateProposal) -> Self {
377 Box::new(UpdateProposalIn {
378 leaf_node: value.leaf_node.into(),
379 })
380 }
381}
382
383#[cfg(any(feature = "test-utils", test))]
384impl From<ProposalIn> for crate::messages::proposals::Proposal {
385 fn from(proposal: ProposalIn) -> Self {
386 match proposal {
387 ProposalIn::Add(add) => Self::Add((*add).into()),
388 ProposalIn::Update(update) => Self::Update((*update).into()),
389 ProposalIn::Remove(remove) => Self::Remove(remove),
390 ProposalIn::PreSharedKey(psk) => Self::PreSharedKey(psk),
391 ProposalIn::ReInit(reinit) => Self::ReInit(reinit),
392 ProposalIn::ExternalInit(external_init) => Self::ExternalInit(external_init),
393 ProposalIn::GroupContextExtensions(group_context_extension) => {
394 Self::GroupContextExtensions((*group_context_extension).into())
395 }
396 #[cfg(feature = "extensions-draft-08")]
397 ProposalIn::AppDataUpdate(app_data_update) => Self::AppDataUpdate(app_data_update),
398 ProposalIn::SelfRemove => Self::SelfRemove,
399 #[cfg(feature = "extensions-draft-08")]
400 ProposalIn::AppEphemeral(app_ephemeral) => Self::AppEphemeral(app_ephemeral),
401 ProposalIn::Custom(other) => Self::Custom(other),
402 }
403 }
404}
405
406impl From<crate::messages::proposals::Proposal> for ProposalIn {
407 fn from(proposal: crate::messages::proposals::Proposal) -> Self {
408 match proposal {
409 Proposal::Add(add) => Self::Add((*add).into()),
410 Proposal::Update(update) => Self::Update((*update).into()),
411 Proposal::Remove(remove) => Self::Remove(remove),
412 Proposal::PreSharedKey(psk) => Self::PreSharedKey(psk),
413 Proposal::ReInit(reinit) => Self::ReInit(reinit),
414 Proposal::ExternalInit(external_init) => Self::ExternalInit(external_init),
415 Proposal::GroupContextExtensions(group_context_extension) => {
416 Self::GroupContextExtensions((*group_context_extension).into())
417 }
418 #[cfg(feature = "extensions-draft-08")]
419 Proposal::AppDataUpdate(app_data_update) => Self::AppDataUpdate(app_data_update),
420 Proposal::SelfRemove => Self::SelfRemove,
421 #[cfg(feature = "extensions-draft-08")]
422 Proposal::AppEphemeral(app_ephemeral) => Self::AppEphemeral(app_ephemeral),
423 Proposal::Custom(other) => Self::Custom(other),
424 }
425 }
426}
427
428#[cfg(any(feature = "test-utils", test))]
429impl From<ProposalOrRefIn> for crate::messages::proposals::ProposalOrRef {
430 fn from(proposal: ProposalOrRefIn) -> Self {
431 match proposal {
432 ProposalOrRefIn::Proposal(proposal) => Self::Proposal(Box::new((*proposal).into())),
433 ProposalOrRefIn::Reference(reference) => Self::Reference(reference),
434 }
435 }
436}
437
438impl From<crate::messages::proposals::ProposalOrRef> for ProposalOrRefIn {
439 fn from(proposal: crate::messages::proposals::ProposalOrRef) -> Self {
440 match proposal {
441 crate::messages::proposals::ProposalOrRef::Proposal(proposal) => {
442 Self::Proposal(Box::new((*proposal).into()))
443 }
444 crate::messages::proposals::ProposalOrRef::Reference(reference) => {
445 Self::Reference(reference)
446 }
447 }
448 }
449}
450
451#[derive(
453 Debug,
454 PartialEq,
455 Clone,
456 Serialize,
457 Deserialize,
458 TlsSerialize,
459 TlsDeserialize,
460 TlsDeserializeBytes,
461 TlsSize,
462)]
463pub struct GroupContextExtensionProposalIn {
464 extensions_tbv: Extensions<AnyObject>,
465}
466
467impl GroupContextExtensionProposalIn {
468 pub(crate) fn validate(self) -> Result<GroupContextExtensionProposal, ValidationError> {
469 let group_context_extensions = self.extensions_tbv;
470 Ok(GroupContextExtensionProposal::new(
471 group_context_extensions
472 .try_into()
473 .map_err(InvalidExtensionError::from)?,
474 ))
475 }
476}