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, AppAckProposal, ExternalInitProposal, GroupContextExtensionProposal,
22 PreSharedKeyProposal, Proposal, ProposalOrRef, ProposalType, ReInitProposal,
23 RemoveProposal, UpdateProposal,
24 },
25 CustomProposal,
26};
27
28#[allow(clippy::large_enum_variant)]
48#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
49#[allow(missing_docs)]
50#[repr(u16)]
51pub enum ProposalIn {
52 Add(AddProposalIn),
53 Update(UpdateProposalIn),
54 Remove(RemoveProposal),
55 PreSharedKey(PreSharedKeyProposal),
56 ReInit(ReInitProposal),
57 ExternalInit(ExternalInitProposal),
58 GroupContextExtensions(GroupContextExtensionProposal),
59 AppAck(AppAckProposal),
63 SelfRemove,
65 Custom(CustomProposal),
66}
67
68impl ProposalIn {
69 pub fn proposal_type(&self) -> ProposalType {
71 match self {
72 ProposalIn::Add(_) => ProposalType::Add,
73 ProposalIn::Update(_) => ProposalType::Update,
74 ProposalIn::Remove(_) => ProposalType::Remove,
75 ProposalIn::PreSharedKey(_) => ProposalType::PreSharedKey,
76 ProposalIn::ReInit(_) => ProposalType::Reinit,
77 ProposalIn::ExternalInit(_) => ProposalType::ExternalInit,
78 ProposalIn::GroupContextExtensions(_) => ProposalType::GroupContextExtensions,
79 ProposalIn::AppAck(_) => ProposalType::AppAck,
80 ProposalIn::SelfRemove => ProposalType::SelfRemove,
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) => {
102 Proposal::Add(add.validate(crypto, protocol_version, ciphersuite)?)
103 }
104 ProposalIn::Update(update) => {
105 let sender_context =
106 sender_context.ok_or(ValidationError::CommitterIncludedOwnUpdate)?;
107 Proposal::Update(update.validate(crypto, ciphersuite, sender_context)?)
108 }
109 ProposalIn::Remove(remove) => Proposal::Remove(remove),
110 ProposalIn::PreSharedKey(psk) => Proposal::PreSharedKey(psk),
111 ProposalIn::ReInit(reinit) => Proposal::ReInit(reinit),
112 ProposalIn::ExternalInit(external_init) => Proposal::ExternalInit(external_init),
113 ProposalIn::GroupContextExtensions(group_context_extension) => {
114 Proposal::GroupContextExtensions(group_context_extension)
115 }
116 ProposalIn::AppAck(app_ack) => Proposal::AppAck(app_ack),
117 ProposalIn::SelfRemove => Proposal::SelfRemove,
118 ProposalIn::Custom(custom) => Proposal::Custom(custom),
119 })
120 }
121}
122
123#[derive(
134 Debug,
135 PartialEq,
136 Clone,
137 Serialize,
138 Deserialize,
139 TlsSerialize,
140 TlsDeserialize,
141 TlsDeserializeBytes,
142 TlsSize,
143)]
144pub struct AddProposalIn {
145 key_package: KeyPackageIn,
146}
147
148impl AddProposalIn {
149 pub(crate) fn unverified_credential(&self) -> CredentialWithKey {
150 self.key_package.unverified_credential()
151 }
152
153 pub(crate) fn validate(
155 self,
156 crypto: &impl OpenMlsCrypto,
157 protocol_version: ProtocolVersion,
158 ciphersuite: Ciphersuite,
159 ) -> Result<AddProposal, ValidationError> {
160 let key_package = self.key_package.validate(crypto, protocol_version)?;
161 if key_package.ciphersuite() != ciphersuite {
163 return Err(ValidationError::InvalidAddProposalCiphersuite);
164 }
165 Ok(AddProposal { key_package })
166 }
167}
168
169#[derive(
181 Debug,
182 PartialEq,
183 Eq,
184 Clone,
185 Serialize,
186 Deserialize,
187 TlsDeserialize,
188 TlsDeserializeBytes,
189 TlsSerialize,
190 TlsSize,
191)]
192pub struct UpdateProposalIn {
193 leaf_node: LeafNodeIn,
194}
195
196impl UpdateProposalIn {
197 pub(crate) fn validate(
199 self,
200 crypto: &impl OpenMlsCrypto,
201 ciphersuite: Ciphersuite,
202 sender_context: SenderContext,
203 ) -> Result<UpdateProposal, ValidationError> {
204 let leaf_node = match self.leaf_node.into_verifiable_leaf_node() {
205 VerifiableLeafNode::Update(mut leaf_node) => {
206 let tree_position = match sender_context {
207 SenderContext::Member((group_id, leaf_index)) => {
208 TreePosition::new(group_id, leaf_index)
209 }
210 _ => return Err(ValidationError::InvalidSenderType),
211 };
212 leaf_node.add_tree_position(tree_position);
213 let pk = &leaf_node
214 .signature_key()
215 .clone()
216 .into_signature_public_key_enriched(ciphersuite.signature_algorithm());
217
218 leaf_node
219 .verify(crypto, pk)
220 .map_err(|_| ValidationError::InvalidLeafNodeSignature)?
221 }
222 _ => return Err(ValidationError::InvalidLeafNodeSourceType),
223 };
224
225 Ok(UpdateProposal { leaf_node })
226 }
227}
228
229#[derive(
233 Debug,
234 PartialEq,
235 Clone,
236 Serialize,
237 Deserialize,
238 TlsSerialize,
239 TlsDeserialize,
240 TlsDeserializeBytes,
241 TlsSize,
242)]
243#[repr(u8)]
244#[allow(missing_docs)]
245#[allow(clippy::large_enum_variant)]
246pub(crate) enum ProposalOrRefIn {
247 #[tls_codec(discriminant = 1)]
248 Proposal(ProposalIn),
249 Reference(ProposalRef),
250}
251
252impl ProposalOrRefIn {
253 pub(crate) fn validate(
255 self,
256 crypto: &impl OpenMlsCrypto,
257 ciphersuite: Ciphersuite,
258 protocol_version: ProtocolVersion,
259 ) -> Result<ProposalOrRef, ValidationError> {
260 Ok(match self {
261 ProposalOrRefIn::Proposal(proposal_in) => ProposalOrRef::Proposal(
262 proposal_in.validate(crypto, ciphersuite, None, protocol_version)?,
263 ),
264 ProposalOrRefIn::Reference(reference) => ProposalOrRef::Reference(reference),
265 })
266 }
267}
268
269#[cfg(any(feature = "test-utils", test))]
272impl From<AddProposalIn> for crate::messages::proposals::AddProposal {
273 fn from(value: AddProposalIn) -> Self {
274 Self {
275 key_package: value.key_package.into(),
276 }
277 }
278}
279
280impl From<crate::messages::proposals::AddProposal> for AddProposalIn {
281 fn from(value: crate::messages::proposals::AddProposal) -> Self {
282 Self {
283 key_package: value.key_package.into(),
284 }
285 }
286}
287
288#[cfg(any(feature = "test-utils", test))]
291impl From<UpdateProposalIn> for crate::messages::proposals::UpdateProposal {
292 fn from(value: UpdateProposalIn) -> Self {
293 Self {
294 leaf_node: value.leaf_node.into(),
295 }
296 }
297}
298
299impl From<crate::messages::proposals::UpdateProposal> for UpdateProposalIn {
300 fn from(value: crate::messages::proposals::UpdateProposal) -> Self {
301 Self {
302 leaf_node: value.leaf_node.into(),
303 }
304 }
305}
306
307#[cfg(any(feature = "test-utils", test))]
308impl From<ProposalIn> for crate::messages::proposals::Proposal {
309 fn from(proposal: ProposalIn) -> Self {
310 match proposal {
311 ProposalIn::Add(add) => Self::Add(add.into()),
312 ProposalIn::Update(update) => Self::Update(update.into()),
313 ProposalIn::Remove(remove) => Self::Remove(remove),
314 ProposalIn::PreSharedKey(psk) => Self::PreSharedKey(psk),
315 ProposalIn::ReInit(reinit) => Self::ReInit(reinit),
316 ProposalIn::ExternalInit(external_init) => Self::ExternalInit(external_init),
317 ProposalIn::GroupContextExtensions(group_context_extension) => {
318 Self::GroupContextExtensions(group_context_extension)
319 }
320 ProposalIn::AppAck(app_ack) => Self::AppAck(app_ack),
321 ProposalIn::SelfRemove => Self::SelfRemove,
322 ProposalIn::Custom(other) => Self::Custom(other),
323 }
324 }
325}
326
327impl From<crate::messages::proposals::Proposal> for ProposalIn {
328 fn from(proposal: crate::messages::proposals::Proposal) -> Self {
329 match proposal {
330 Proposal::Add(add) => Self::Add(add.into()),
331 Proposal::Update(update) => Self::Update(update.into()),
332 Proposal::Remove(remove) => Self::Remove(remove),
333 Proposal::PreSharedKey(psk) => Self::PreSharedKey(psk),
334 Proposal::ReInit(reinit) => Self::ReInit(reinit),
335 Proposal::ExternalInit(external_init) => Self::ExternalInit(external_init),
336 Proposal::GroupContextExtensions(group_context_extension) => {
337 Self::GroupContextExtensions(group_context_extension)
338 }
339 Proposal::AppAck(app_ack) => Self::AppAck(app_ack),
340 Proposal::SelfRemove => Self::SelfRemove,
341 Proposal::Custom(other) => Self::Custom(other),
342 }
343 }
344}
345
346#[cfg(any(feature = "test-utils", test))]
347impl From<ProposalOrRefIn> for crate::messages::proposals::ProposalOrRef {
348 fn from(proposal: ProposalOrRefIn) -> Self {
349 match proposal {
350 ProposalOrRefIn::Proposal(proposal) => Self::Proposal(proposal.into()),
351 ProposalOrRefIn::Reference(reference) => Self::Reference(reference),
352 }
353 }
354}
355
356impl From<crate::messages::proposals::ProposalOrRef> for ProposalOrRefIn {
357 fn from(proposal: crate::messages::proposals::ProposalOrRef) -> Self {
358 match proposal {
359 crate::messages::proposals::ProposalOrRef::Proposal(proposal) => {
360 Self::Proposal(proposal.into())
361 }
362 crate::messages::proposals::ProposalOrRef::Reference(reference) => {
363 Self::Reference(reference)
364 }
365 }
366 }
367}