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#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
48#[allow(missing_docs)]
49#[repr(u16)]
50pub enum ProposalIn {
51 Add(Box<AddProposalIn>),
52 Update(Box<UpdateProposalIn>),
53 Remove(Box<RemoveProposal>),
54 PreSharedKey(Box<PreSharedKeyProposal>),
55 ReInit(Box<ReInitProposal>),
56 ExternalInit(Box<ExternalInitProposal>),
57 GroupContextExtensions(Box<GroupContextExtensionProposal>),
58 AppAck(Box<AppAckProposal>),
62 SelfRemove,
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::AppAck(_) => ProposalType::AppAck,
79 ProposalIn::SelfRemove => ProposalType::SelfRemove,
80 ProposalIn::Custom(custom_proposal) => {
81 ProposalType::Custom(custom_proposal.proposal_type())
82 }
83 }
84 }
85
86 pub fn is_path_required(&self) -> bool {
88 self.proposal_type().is_path_required()
89 }
90
91 pub(crate) fn validate(
93 self,
94 crypto: &impl OpenMlsCrypto,
95 ciphersuite: Ciphersuite,
96 sender_context: Option<SenderContext>,
97 protocol_version: ProtocolVersion,
98 ) -> Result<Proposal, ValidationError> {
99 Ok(match self {
100 ProposalIn::Add(add) => Proposal::Add(Box::new(add.validate(
101 crypto,
102 protocol_version,
103 ciphersuite,
104 )?)),
105 ProposalIn::Update(update) => {
106 let sender_context =
107 sender_context.ok_or(ValidationError::CommitterIncludedOwnUpdate)?;
108 Proposal::Update(Box::new(update.validate(
109 crypto,
110 ciphersuite,
111 sender_context,
112 )?))
113 }
114 ProposalIn::Remove(remove) => Proposal::Remove(remove),
115 ProposalIn::PreSharedKey(psk) => Proposal::PreSharedKey(psk),
116 ProposalIn::ReInit(reinit) => Proposal::ReInit(reinit),
117 ProposalIn::ExternalInit(external_init) => Proposal::ExternalInit(external_init),
118 ProposalIn::GroupContextExtensions(group_context_extension) => {
119 Proposal::GroupContextExtensions(group_context_extension)
120 }
121 ProposalIn::AppAck(app_ack) => Proposal::AppAck(app_ack),
122 ProposalIn::SelfRemove => Proposal::SelfRemove,
123 ProposalIn::Custom(custom) => Proposal::Custom(custom),
124 })
125 }
126}
127
128#[derive(
139 Debug,
140 PartialEq,
141 Clone,
142 Serialize,
143 Deserialize,
144 TlsSerialize,
145 TlsDeserialize,
146 TlsDeserializeBytes,
147 TlsSize,
148)]
149pub struct AddProposalIn {
150 key_package: KeyPackageIn,
151}
152
153impl AddProposalIn {
154 pub(crate) fn unverified_credential(&self) -> CredentialWithKey {
155 self.key_package.unverified_credential()
156 }
157
158 pub(crate) fn validate(
160 self,
161 crypto: &impl OpenMlsCrypto,
162 protocol_version: ProtocolVersion,
163 ciphersuite: Ciphersuite,
164 ) -> Result<AddProposal, ValidationError> {
165 let key_package = self.key_package.validate(crypto, protocol_version)?;
166 if key_package.ciphersuite() != ciphersuite {
168 return Err(ValidationError::InvalidAddProposalCiphersuite);
169 }
170 Ok(AddProposal { key_package })
171 }
172}
173
174#[derive(
186 Debug,
187 PartialEq,
188 Eq,
189 Clone,
190 Serialize,
191 Deserialize,
192 TlsDeserialize,
193 TlsDeserializeBytes,
194 TlsSerialize,
195 TlsSize,
196)]
197pub struct UpdateProposalIn {
198 leaf_node: LeafNodeIn,
199}
200
201impl UpdateProposalIn {
202 pub(crate) fn validate(
204 self,
205 crypto: &impl OpenMlsCrypto,
206 ciphersuite: Ciphersuite,
207 sender_context: SenderContext,
208 ) -> Result<UpdateProposal, ValidationError> {
209 let leaf_node = match self.leaf_node.into_verifiable_leaf_node() {
210 VerifiableLeafNode::Update(mut leaf_node) => {
211 let tree_position = match sender_context {
212 SenderContext::Member((group_id, leaf_index)) => {
213 TreePosition::new(group_id, leaf_index)
214 }
215 _ => return Err(ValidationError::InvalidSenderType),
216 };
217 leaf_node.add_tree_position(tree_position);
218 let pk = &leaf_node
219 .signature_key()
220 .clone()
221 .into_signature_public_key_enriched(ciphersuite.signature_algorithm());
222
223 leaf_node
224 .verify(crypto, pk)
225 .map_err(|_| ValidationError::InvalidLeafNodeSignature)?
226 }
227 _ => return Err(ValidationError::InvalidLeafNodeSourceType),
228 };
229
230 Ok(UpdateProposal { leaf_node })
231 }
232}
233
234#[derive(
238 Debug,
239 PartialEq,
240 Clone,
241 Serialize,
242 Deserialize,
243 TlsSerialize,
244 TlsDeserialize,
245 TlsDeserializeBytes,
246 TlsSize,
247)]
248#[repr(u8)]
249#[allow(missing_docs)]
250pub(crate) enum ProposalOrRefIn {
251 #[tls_codec(discriminant = 1)]
252 Proposal(Box<ProposalIn>),
253 Reference(Box<ProposalRef>),
254}
255
256impl ProposalOrRefIn {
257 pub(crate) fn validate(
259 self,
260 crypto: &impl OpenMlsCrypto,
261 ciphersuite: Ciphersuite,
262 protocol_version: ProtocolVersion,
263 ) -> Result<ProposalOrRef, ValidationError> {
264 Ok(match self {
265 ProposalOrRefIn::Proposal(proposal_in) => ProposalOrRef::Proposal(Box::new(
266 proposal_in.validate(crypto, ciphersuite, None, protocol_version)?,
267 )),
268 ProposalOrRefIn::Reference(reference) => ProposalOrRef::Reference(reference),
269 })
270 }
271}
272
273#[cfg(any(feature = "test-utils", test))]
276impl From<AddProposalIn> for AddProposal {
277 fn from(value: AddProposalIn) -> Self {
278 Self {
279 key_package: value.key_package.into(),
280 }
281 }
282}
283
284#[cfg(any(feature = "test-utils", test))]
285impl From<AddProposalIn> for Box<AddProposal> {
286 fn from(value: AddProposalIn) -> Self {
287 Box::new(AddProposal {
288 key_package: value.key_package.into(),
289 })
290 }
291}
292
293impl From<AddProposal> for Box<AddProposalIn> {
294 fn from(value: AddProposal) -> Self {
295 Box::new(AddProposalIn {
296 key_package: value.key_package.into(),
297 })
298 }
299}
300
301#[cfg(any(feature = "test-utils", test))]
304impl From<UpdateProposalIn> for UpdateProposal {
305 fn from(value: UpdateProposalIn) -> Self {
306 Self {
307 leaf_node: value.leaf_node.into(),
308 }
309 }
310}
311
312impl From<UpdateProposal> for UpdateProposalIn {
313 fn from(value: UpdateProposal) -> Self {
314 Self {
315 leaf_node: value.leaf_node.into(),
316 }
317 }
318}
319
320#[cfg(any(feature = "test-utils", test))]
321impl From<UpdateProposalIn> for Box<UpdateProposal> {
322 fn from(value: UpdateProposalIn) -> Self {
323 Box::new(UpdateProposal {
324 leaf_node: value.leaf_node.into(),
325 })
326 }
327}
328
329impl From<UpdateProposal> for Box<UpdateProposalIn> {
330 fn from(value: UpdateProposal) -> Self {
331 Box::new(UpdateProposalIn {
332 leaf_node: value.leaf_node.into(),
333 })
334 }
335}
336
337#[cfg(any(feature = "test-utils", test))]
338impl From<ProposalIn> for crate::messages::proposals::Proposal {
339 fn from(proposal: ProposalIn) -> Self {
340 match proposal {
341 ProposalIn::Add(add) => Self::Add((*add).into()),
342 ProposalIn::Update(update) => Self::Update((*update).into()),
343 ProposalIn::Remove(remove) => Self::Remove(remove),
344 ProposalIn::PreSharedKey(psk) => Self::PreSharedKey(psk),
345 ProposalIn::ReInit(reinit) => Self::ReInit(reinit),
346 ProposalIn::ExternalInit(external_init) => Self::ExternalInit(external_init),
347 ProposalIn::GroupContextExtensions(group_context_extension) => {
348 Self::GroupContextExtensions(group_context_extension)
349 }
350 ProposalIn::AppAck(app_ack) => Self::AppAck(app_ack),
351 ProposalIn::SelfRemove => Self::SelfRemove,
352 ProposalIn::Custom(other) => Self::Custom(other),
353 }
354 }
355}
356
357impl From<crate::messages::proposals::Proposal> for ProposalIn {
358 fn from(proposal: crate::messages::proposals::Proposal) -> Self {
359 match proposal {
360 Proposal::Add(add) => Self::Add((*add).into()),
361 Proposal::Update(update) => Self::Update((*update).into()),
362 Proposal::Remove(remove) => Self::Remove(remove),
363 Proposal::PreSharedKey(psk) => Self::PreSharedKey(psk),
364 Proposal::ReInit(reinit) => Self::ReInit(reinit),
365 Proposal::ExternalInit(external_init) => Self::ExternalInit(external_init),
366 Proposal::GroupContextExtensions(group_context_extension) => {
367 Self::GroupContextExtensions(group_context_extension)
368 }
369 Proposal::AppAck(app_ack) => Self::AppAck(app_ack),
370 Proposal::SelfRemove => Self::SelfRemove,
371 Proposal::Custom(other) => Self::Custom(other),
372 }
373 }
374}
375
376#[cfg(any(feature = "test-utils", test))]
377impl From<ProposalOrRefIn> for crate::messages::proposals::ProposalOrRef {
378 fn from(proposal: ProposalOrRefIn) -> Self {
379 match proposal {
380 ProposalOrRefIn::Proposal(proposal) => Self::Proposal(Box::new((*proposal).into())),
381 ProposalOrRefIn::Reference(reference) => Self::Reference(reference),
382 }
383 }
384}
385
386impl From<crate::messages::proposals::ProposalOrRef> for ProposalOrRefIn {
387 fn from(proposal: crate::messages::proposals::ProposalOrRef) -> Self {
388 match proposal {
389 crate::messages::proposals::ProposalOrRef::Proposal(proposal) => {
390 Self::Proposal(Box::new((*proposal).into()))
391 }
392 crate::messages::proposals::ProposalOrRef::Reference(reference) => {
393 Self::Reference(reference)
394 }
395 }
396 }
397}