1use crate::{
6 ciphersuite::{hash_ref::ProposalRef, signable::Verifiable},
7 credentials::CredentialWithKey,
8 framing::SenderContext,
9 group::errors::ValidationError,
10 key_packages::*,
11 treesync::node::leaf_node::{LeafNodeIn, TreePosition, VerifiableLeafNode},
12 versions::ProtocolVersion,
13};
14
15use openmls_traits::{crypto::OpenMlsCrypto, types::Ciphersuite};
16use serde::{Deserialize, Serialize};
17use tls_codec::{TlsDeserialize, TlsDeserializeBytes, TlsSerialize, TlsSize};
18
19use super::{
20 proposals::{
21 AddProposal, ExternalInitProposal, GroupContextExtensionProposal, PreSharedKeyProposal,
22 Proposal, ProposalOrRef, ProposalType, ReInitProposal, RemoveProposal, UpdateProposal,
23 },
24 CustomProposal,
25};
26
27#[cfg(feature = "extensions-draft-08")]
28use super::proposals::AppEphemeralProposal;
29
30#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
50#[allow(missing_docs)]
51#[repr(u16)]
52pub enum ProposalIn {
53 Add(Box<AddProposalIn>),
54 Update(Box<UpdateProposalIn>),
55 Remove(Box<RemoveProposal>),
56 PreSharedKey(Box<PreSharedKeyProposal>),
57 ReInit(Box<ReInitProposal>),
58 ExternalInit(Box<ExternalInitProposal>),
59 GroupContextExtensions(Box<GroupContextExtensionProposal>),
60 SelfRemove,
62 #[cfg(feature = "extensions-draft-08")]
63 AppEphemeral(Box<AppEphemeralProposal>),
64 Custom(Box<CustomProposal>),
65}
66
67impl ProposalIn {
68 pub fn proposal_type(&self) -> ProposalType {
70 match self {
71 ProposalIn::Add(_) => ProposalType::Add,
72 ProposalIn::Update(_) => ProposalType::Update,
73 ProposalIn::Remove(_) => ProposalType::Remove,
74 ProposalIn::PreSharedKey(_) => ProposalType::PreSharedKey,
75 ProposalIn::ReInit(_) => ProposalType::Reinit,
76 ProposalIn::ExternalInit(_) => ProposalType::ExternalInit,
77 ProposalIn::GroupContextExtensions(_) => ProposalType::GroupContextExtensions,
78 ProposalIn::SelfRemove => ProposalType::SelfRemove,
79 #[cfg(feature = "extensions-draft-08")]
80 ProposalIn::AppEphemeral(_) => ProposalType::AppEphemeral,
81 ProposalIn::Custom(custom_proposal) => {
82 ProposalType::Custom(custom_proposal.proposal_type())
83 }
84 }
85 }
86
87 pub fn is_path_required(&self) -> bool {
89 self.proposal_type().is_path_required()
90 }
91
92 pub(crate) fn validate(
94 self,
95 crypto: &impl OpenMlsCrypto,
96 ciphersuite: Ciphersuite,
97 sender_context: Option<SenderContext>,
98 protocol_version: ProtocolVersion,
99 ) -> Result<Proposal, ValidationError> {
100 Ok(match self {
101 ProposalIn::Add(add) => Proposal::Add(Box::new(add.validate(
102 crypto,
103 protocol_version,
104 ciphersuite,
105 )?)),
106 ProposalIn::Update(update) => {
107 let sender_context =
108 sender_context.ok_or(ValidationError::CommitterIncludedOwnUpdate)?;
109 Proposal::Update(Box::new(update.validate(
110 crypto,
111 ciphersuite,
112 sender_context,
113 )?))
114 }
115 ProposalIn::Remove(remove) => Proposal::Remove(remove),
116 ProposalIn::PreSharedKey(psk) => Proposal::PreSharedKey(psk),
117 ProposalIn::ReInit(reinit) => Proposal::ReInit(reinit),
118 ProposalIn::ExternalInit(external_init) => Proposal::ExternalInit(external_init),
119 ProposalIn::GroupContextExtensions(group_context_extension) => {
120 Proposal::GroupContextExtensions(group_context_extension)
121 }
122 ProposalIn::SelfRemove => Proposal::SelfRemove,
123 #[cfg(feature = "extensions-draft-08")]
124 ProposalIn::AppEphemeral(app_ephemeral) => Proposal::AppEphemeral(app_ephemeral),
125 ProposalIn::Custom(custom) => Proposal::Custom(custom),
126 })
127 }
128}
129
130#[derive(
141 Debug,
142 PartialEq,
143 Clone,
144 Serialize,
145 Deserialize,
146 TlsSerialize,
147 TlsDeserialize,
148 TlsDeserializeBytes,
149 TlsSize,
150)]
151pub struct AddProposalIn {
152 key_package: KeyPackageIn,
153}
154
155impl AddProposalIn {
156 pub(crate) fn unverified_credential(&self) -> CredentialWithKey {
157 self.key_package.unverified_credential()
158 }
159
160 pub(crate) fn validate(
162 self,
163 crypto: &impl OpenMlsCrypto,
164 protocol_version: ProtocolVersion,
165 ciphersuite: Ciphersuite,
166 ) -> Result<AddProposal, ValidationError> {
167 let key_package = self.key_package.validate(crypto, protocol_version)?;
168 if key_package.ciphersuite() != ciphersuite {
170 return Err(ValidationError::InvalidAddProposalCiphersuite);
171 }
172 Ok(AddProposal { key_package })
173 }
174}
175
176#[derive(
188 Debug,
189 PartialEq,
190 Eq,
191 Clone,
192 Serialize,
193 Deserialize,
194 TlsDeserialize,
195 TlsDeserializeBytes,
196 TlsSerialize,
197 TlsSize,
198)]
199pub struct UpdateProposalIn {
200 leaf_node: LeafNodeIn,
201}
202
203impl UpdateProposalIn {
204 pub(crate) fn validate(
206 self,
207 crypto: &impl OpenMlsCrypto,
208 ciphersuite: Ciphersuite,
209 sender_context: SenderContext,
210 ) -> Result<UpdateProposal, ValidationError> {
211 let leaf_node = match self.leaf_node.into_verifiable_leaf_node() {
212 VerifiableLeafNode::Update(mut leaf_node) => {
213 let tree_position = match sender_context {
214 SenderContext::Member((group_id, leaf_index)) => {
215 TreePosition::new(group_id, leaf_index)
216 }
217 _ => return Err(ValidationError::InvalidSenderType),
218 };
219 leaf_node.add_tree_position(tree_position);
220 let pk = &leaf_node
221 .signature_key()
222 .clone()
223 .into_signature_public_key_enriched(ciphersuite.signature_algorithm());
224
225 leaf_node
226 .verify(crypto, pk)
227 .map_err(|_| ValidationError::InvalidLeafNodeSignature)?
228 }
229 _ => return Err(ValidationError::InvalidLeafNodeSourceType),
230 };
231
232 Ok(UpdateProposal { leaf_node })
233 }
234}
235
236#[derive(
240 Debug,
241 PartialEq,
242 Clone,
243 Serialize,
244 Deserialize,
245 TlsSerialize,
246 TlsDeserialize,
247 TlsDeserializeBytes,
248 TlsSize,
249)]
250#[repr(u8)]
251#[allow(missing_docs)]
252pub(crate) enum ProposalOrRefIn {
253 #[tls_codec(discriminant = 1)]
254 Proposal(Box<ProposalIn>),
255 Reference(Box<ProposalRef>),
256}
257
258impl ProposalOrRefIn {
259 pub(crate) fn validate(
261 self,
262 crypto: &impl OpenMlsCrypto,
263 ciphersuite: Ciphersuite,
264 protocol_version: ProtocolVersion,
265 ) -> Result<ProposalOrRef, ValidationError> {
266 Ok(match self {
267 ProposalOrRefIn::Proposal(proposal_in) => ProposalOrRef::Proposal(Box::new(
268 proposal_in.validate(crypto, ciphersuite, None, protocol_version)?,
269 )),
270 ProposalOrRefIn::Reference(reference) => ProposalOrRef::Reference(reference),
271 })
272 }
273}
274
275#[cfg(any(feature = "test-utils", test))]
278impl From<AddProposalIn> for AddProposal {
279 fn from(value: AddProposalIn) -> Self {
280 Self {
281 key_package: value.key_package.into(),
282 }
283 }
284}
285
286#[cfg(any(feature = "test-utils", test))]
287impl From<AddProposalIn> for Box<AddProposal> {
288 fn from(value: AddProposalIn) -> Self {
289 Box::new(AddProposal {
290 key_package: value.key_package.into(),
291 })
292 }
293}
294
295impl From<AddProposal> for Box<AddProposalIn> {
296 fn from(value: AddProposal) -> Self {
297 Box::new(AddProposalIn {
298 key_package: value.key_package.into(),
299 })
300 }
301}
302
303#[cfg(any(feature = "test-utils", test))]
306impl From<UpdateProposalIn> for UpdateProposal {
307 fn from(value: UpdateProposalIn) -> Self {
308 Self {
309 leaf_node: value.leaf_node.into(),
310 }
311 }
312}
313
314impl From<UpdateProposal> for UpdateProposalIn {
315 fn from(value: UpdateProposal) -> Self {
316 Self {
317 leaf_node: value.leaf_node.into(),
318 }
319 }
320}
321
322#[cfg(any(feature = "test-utils", test))]
323impl From<UpdateProposalIn> for Box<UpdateProposal> {
324 fn from(value: UpdateProposalIn) -> Self {
325 Box::new(UpdateProposal {
326 leaf_node: value.leaf_node.into(),
327 })
328 }
329}
330
331impl From<UpdateProposal> for Box<UpdateProposalIn> {
332 fn from(value: UpdateProposal) -> Self {
333 Box::new(UpdateProposalIn {
334 leaf_node: value.leaf_node.into(),
335 })
336 }
337}
338
339#[cfg(any(feature = "test-utils", test))]
340impl From<ProposalIn> for crate::messages::proposals::Proposal {
341 fn from(proposal: ProposalIn) -> Self {
342 match proposal {
343 ProposalIn::Add(add) => Self::Add((*add).into()),
344 ProposalIn::Update(update) => Self::Update((*update).into()),
345 ProposalIn::Remove(remove) => Self::Remove(remove),
346 ProposalIn::PreSharedKey(psk) => Self::PreSharedKey(psk),
347 ProposalIn::ReInit(reinit) => Self::ReInit(reinit),
348 ProposalIn::ExternalInit(external_init) => Self::ExternalInit(external_init),
349 ProposalIn::GroupContextExtensions(group_context_extension) => {
350 Self::GroupContextExtensions(group_context_extension)
351 }
352 ProposalIn::SelfRemove => Self::SelfRemove,
353 #[cfg(feature = "extensions-draft-08")]
354 ProposalIn::AppEphemeral(app_ephemeral) => Self::AppEphemeral(app_ephemeral),
355 ProposalIn::Custom(other) => Self::Custom(other),
356 }
357 }
358}
359
360impl From<crate::messages::proposals::Proposal> for ProposalIn {
361 fn from(proposal: crate::messages::proposals::Proposal) -> Self {
362 match proposal {
363 Proposal::Add(add) => Self::Add((*add).into()),
364 Proposal::Update(update) => Self::Update((*update).into()),
365 Proposal::Remove(remove) => Self::Remove(remove),
366 Proposal::PreSharedKey(psk) => Self::PreSharedKey(psk),
367 Proposal::ReInit(reinit) => Self::ReInit(reinit),
368 Proposal::ExternalInit(external_init) => Self::ExternalInit(external_init),
369 Proposal::GroupContextExtensions(group_context_extension) => {
370 Self::GroupContextExtensions(group_context_extension)
371 }
372 Proposal::SelfRemove => Self::SelfRemove,
373 #[cfg(feature = "extensions-draft-08")]
374 Proposal::AppEphemeral(app_ephemeral) => Self::AppEphemeral(app_ephemeral),
375 Proposal::Custom(other) => Self::Custom(other),
376 }
377 }
378}
379
380#[cfg(any(feature = "test-utils", test))]
381impl From<ProposalOrRefIn> for crate::messages::proposals::ProposalOrRef {
382 fn from(proposal: ProposalOrRefIn) -> Self {
383 match proposal {
384 ProposalOrRefIn::Proposal(proposal) => Self::Proposal(Box::new((*proposal).into())),
385 ProposalOrRefIn::Reference(reference) => Self::Reference(reference),
386 }
387 }
388}
389
390impl From<crate::messages::proposals::ProposalOrRef> for ProposalOrRefIn {
391 fn from(proposal: crate::messages::proposals::ProposalOrRef) -> Self {
392 match proposal {
393 crate::messages::proposals::ProposalOrRef::Proposal(proposal) => {
394 Self::Proposal(Box::new((*proposal).into()))
395 }
396 crate::messages::proposals::ProposalOrRef::Reference(reference) => {
397 Self::Reference(reference)
398 }
399 }
400 }
401}