1#[cfg(test)]
4use crate::xml::dom::ParsingOptions;
5use crate::xml::dom::{Document, Node};
6#[cfg(test)]
7use base64::{Engine as _, engine::general_purpose::STANDARD};
8
9use crate::document::{
10 DocumentParseSettings, XmlParseWorkBudget, parse_borrowed_with_settings_and_budget,
11};
12
13use super::map_document_error;
14use super::types::{
15 CipherData, EncryptedData, EncryptedDataType, EncryptedKey, EncryptionMethod,
16 MAX_CIPHER_VALUE_BASE64_LEN, ReferenceList, XMLDSIG_NS, XMLENC_NS, XMLENC11_NS, XmlEncError,
17};
18
19#[derive(Clone, Copy)]
20pub(super) struct ParsingPolicy<'a> {
21 key_establishment: &'a crate::policy::KeyEstablishmentPolicy,
22 xml: &'a crate::policy::XmlInputPolicy,
23 resources: &'a crate::policy::ResourcePolicy,
24 uris: Option<&'a crate::policy::UriPolicy>,
25 transforms: Option<&'a crate::policy::TransformPolicy>,
26}
27
28impl<'a> From<&'a crate::policy::EncryptionPolicy> for ParsingPolicy<'a> {
29 fn from(policy: &'a crate::policy::EncryptionPolicy) -> Self {
30 Self {
31 key_establishment: &policy.key_establishment,
32 xml: &policy.xml,
33 resources: &policy.resources,
34 uris: None,
35 transforms: None,
36 }
37 }
38}
39
40impl<'a> From<&'a crate::policy::DecryptionPolicy> for ParsingPolicy<'a> {
41 fn from(policy: &'a crate::policy::DecryptionPolicy) -> Self {
42 Self {
43 key_establishment: &policy.key_establishment,
44 xml: &policy.xml,
45 resources: &policy.resources,
46 uris: Some(&policy.uris),
47 transforms: Some(&policy.transforms),
48 }
49 }
50}
51
52struct ParsedKeyInfo {
53 encapsulation_methods: Vec<crate::key_establishment::EncapsulationMechanism>,
54 source_nodes: Vec<crate::NodeId>,
55 key_name: Option<String>,
56 encrypted_keys: Vec<EncryptedKey>,
57 derived_keys: Vec<super::DerivedKey>,
58 agreement_methods: Vec<super::AgreementMethod>,
59}
60
61struct SharedKeySourceParseBudget<'a> {
62 retained_cipher_bytes: usize,
63 transforms: crate::xmldsig::transforms::XPathSignatureParseBudget,
64 key_info: crate::xmldsig::parse::KeyInfoParsingSession<'a>,
65 reference_ancestry: Vec<(Option<String>, [u8; 32])>,
66}
67
68impl<'a> SharedKeySourceParseBudget<'a> {
69 fn new(resources: &'a crate::policy::ResourcePolicy) -> Result<Self, XmlEncError> {
70 Ok(Self {
71 retained_cipher_bytes: 0,
72 transforms: crate::xmldsig::transforms::XPathSignatureParseBudget::from_resources(
73 resources,
74 ),
75 key_info: crate::xmldsig::parse::KeyInfoParsingSession::new(resources)
76 .map_err(super::agreement::map_key_info_error)?,
77 reference_ancestry: Vec::new(),
78 })
79 }
80}
81
82struct KeySourceParseBudget<'a, 'doc, 'shared> {
83 policy: ParsingPolicy<'a>,
84 provider: &'a dyn crate::provider::CryptoProvider,
85 shared: &'shared mut SharedKeySourceParseBudget<'a>,
86 references: Option<&'a super::CipherReferenceContext<'a>>,
87 xml_parse: Option<&'a XmlParseWorkBudget>,
88 document_base: Option<String>,
89 resolver: Option<crate::xmldsig::uri::UriReferenceResolver<'doc>>,
90 registrations: &'a [crate::IdAttributeRegistration],
91 ancestry: Vec<crate::NodeId>,
92 origins: Vec<Option<crate::NodeId>>,
93 encapsulation_values: Vec<Node<'doc, 'doc>>,
94 encrypted_key_depth: usize,
95 detached: Option<Vec<crate::NodeId>>,
96}
97
98impl<'a, 'doc, 'shared> KeySourceParseBudget<'a, 'doc, 'shared> {
99 fn resolver(
100 &mut self,
101 source: Node<'doc, 'doc>,
102 ) -> &crate::xmldsig::uri::UriReferenceResolver<'doc> {
103 self.resolver.get_or_insert_with(|| {
104 crate::xmldsig::uri::UriReferenceResolver::with_id_registrations(
105 source.document(),
106 self.registrations,
107 )
108 .with_same_document_id_semantics(
109 self.policy.transforms.map_or(Default::default(), |policy| {
110 policy.same_document_id_semantics
111 }),
112 )
113 })
114 }
115
116 fn referenced_node(
117 &mut self,
118 source: Node<'doc, 'doc>,
119 uri: &str,
120 ) -> Result<Node<'doc, 'doc>, XmlEncError> {
121 self.resolver(source)
122 .node_for_same_document_reference(uri)?
123 .ok_or_else(|| {
124 XmlEncError::InvalidStructure(
125 "encryption key reference target is absent or ambiguous".into(),
126 )
127 })
128 }
129 fn new(
130 policy: ParsingPolicy<'a>,
131 registrations: &'a [crate::IdAttributeRegistration],
132 provider: &'a dyn crate::provider::CryptoProvider,
133 shared: &'shared mut SharedKeySourceParseBudget<'a>,
134 ) -> Self {
135 Self {
136 policy,
137 provider,
138 shared,
139 references: None,
140 xml_parse: None,
141 document_base: None,
142 resolver: None,
143 registrations,
144 ancestry: Vec::new(),
145 origins: Vec::new(),
146 encapsulation_values: Vec::new(),
147 encrypted_key_depth: 0,
148 detached: None,
149 }
150 }
151
152 fn charge(&mut self) -> Result<(), XmlEncError> {
153 self.shared
154 .key_info
155 .charge_encryption_source()
156 .map_err(super::agreement::map_key_info_error)
157 }
158
159 fn charge_recipient(
160 &mut self,
161 resources: &crate::policy::ResourcePolicy,
162 existing: usize,
163 ) -> Result<(), XmlEncError> {
164 if existing >= resources.max_encryption_recipients {
165 return Err(crate::policy::PolicyViolation::ResourceLimit {
166 resource: crate::policy::resource_name::ENCRYPTION_RECIPIENTS,
167 maximum: resources.max_encryption_recipients,
168 actual: existing + 1,
169 }
170 .into());
171 }
172 self.charge()
173 }
174}
175
176pub fn parse_encrypted_data(xml: &str) -> Result<EncryptedData, XmlEncError> {
178 parse_encrypted_data_with_policy(xml, &crate::policy::DecryptionPolicy::default())
179}
180
181pub(super) fn parse_encrypted_data_with_policy(
182 xml: &str,
183 policy: &crate::policy::DecryptionPolicy,
184) -> Result<EncryptedData, XmlEncError> {
185 parse_encrypted_data_with_policy_and_backend(xml, policy, crate::XmlBackend::default())
186}
187
188pub(super) fn parse_encrypted_data_with_policy_and_backend(
189 xml: &str,
190 policy: &crate::policy::DecryptionPolicy,
191 backend: crate::XmlBackend,
192) -> Result<EncryptedData, XmlEncError> {
193 let parse_budget = XmlParseWorkBudget::from_resources(&policy.resources);
194 parse_encrypted_data_with_policy_backend_and_budget(xml, policy, backend, &parse_budget)
195}
196
197pub(super) fn parse_encrypted_data_with_policy_backend_and_budget(
198 xml: &str,
199 policy: &crate::policy::DecryptionPolicy,
200 backend: crate::XmlBackend,
201 parse_budget: &XmlParseWorkBudget,
202) -> Result<EncryptedData, XmlEncError> {
203 policy.validate()?;
204 policy.resources.validate_xml_document_len(xml.len())?;
205 let settings =
206 DocumentParseSettings::from_policy(&policy.xml, &policy.resources).with_backend(backend);
207 let document = parse_borrowed_with_settings_and_budget(xml, settings, Some(parse_budget))
208 .map_err(|error| map_document_error(error, settings))?;
209 let references = super::CipherReferenceContext::new(policy, None, backend, &[])?;
210 parse_encrypted_data_node_with_origins(
211 document.root_element(),
212 policy.into(),
213 false,
214 &[],
215 crate::provider::default_provider(),
216 Some(&references),
217 Some(parse_budget),
218 )
219 .map(|(data, _)| data)
220}
221
222pub fn parse_encrypted_data_node_with_policy(
230 node: Node<'_, '_>,
231 policy: &crate::policy::DecryptionPolicy,
232) -> Result<EncryptedData, XmlEncError> {
233 parse_encrypted_data_node_with_policy_and_backend(node, policy, crate::XmlBackend::default())
234}
235
236pub fn parse_encrypted_data_node_with_policy_and_backend(
238 node: Node<'_, '_>,
239 policy: &crate::policy::DecryptionPolicy,
240 backend: crate::XmlBackend,
241) -> Result<EncryptedData, XmlEncError> {
242 parse_encrypted_data_node_with_context(
243 node,
244 policy,
245 backend,
246 crate::provider::default_provider(),
247 &[],
248 )
249}
250
251pub fn parse_encrypted_data_node_with_context(
254 node: Node<'_, '_>,
255 policy: &crate::policy::DecryptionPolicy,
256 backend: crate::XmlBackend,
257 provider: &dyn crate::provider::CryptoProvider,
258 id_attributes: &[crate::IdAttributeRegistration],
259) -> Result<EncryptedData, XmlEncError> {
260 let parse_budget = XmlParseWorkBudget::from_resources(&policy.resources);
261 parse_encrypted_data_node_with_context_and_budget(
262 node,
263 policy,
264 &parse_budget,
265 backend,
266 provider,
267 id_attributes,
268 )
269}
270
271fn parse_encrypted_data_node_with_context_and_budget(
272 node: Node<'_, '_>,
273 policy: &crate::policy::DecryptionPolicy,
274 parse_budget: &XmlParseWorkBudget,
275 backend: crate::XmlBackend,
276 provider: &dyn crate::provider::CryptoProvider,
277 id_attributes: &[crate::IdAttributeRegistration],
278) -> Result<EncryptedData, XmlEncError> {
279 policy.validate()?;
280 let references = super::CipherReferenceContext::new(policy, None, backend, id_attributes)?;
281 let policy = ParsingPolicy::from(policy);
282 validate_node_document_policy(node, policy, parse_budget, backend)?;
283 parse_encrypted_data_node_with_origins(
284 node,
285 policy,
286 false,
287 id_attributes,
288 provider,
289 Some(&references),
290 Some(parse_budget),
291 )
292 .map(|(data, _)| data)
293}
294
295pub struct EncryptedDataInspection {
299 data: EncryptedData,
300 recipient: Option<super::decrypt::EncapsulationRecipient>,
301}
302
303impl EncryptedDataInspection {
304 pub fn data(&self) -> &EncryptedData {
306 &self.data
307 }
308
309 pub fn requires_document_context(&self) -> bool {
312 fn keys_require_context(keys: &[EncryptedKey]) -> bool {
313 keys.iter().any(|key| {
314 key.reference_list.is_some()
315 || matches!(key.cipher_data, CipherData::Reference { .. })
316 || key
317 .sources
318 .derived_keys
319 .iter()
320 .any(|key| key.reference_list.is_some())
321 || keys_require_context(&key.sources.encrypted_keys)
322 })
323 }
324 matches!(self.data.cipher_data, CipherData::Reference { .. })
327 || self
328 .data
329 .derived_keys
330 .iter()
331 .any(|key| key.reference_list.is_some())
332 || keys_require_context(&self.data.encrypted_keys)
333 }
334
335 pub fn recipient_encapsulation(
337 &self,
338 ) -> Option<&crate::key_establishment::EncapsulationMechanism> {
339 self.recipient
340 .as_ref()
341 .map(|recipient| recipient.mechanism(&self.data))
342 }
343}
344
345pub fn inspect_encrypted_data_node_with_context(
348 node: Node<'_, '_>,
349 policy: &crate::policy::DecryptionPolicy,
350 backend: crate::XmlBackend,
351 provider: &dyn crate::provider::CryptoProvider,
352 id_attributes: &[crate::IdAttributeRegistration],
353) -> Result<EncryptedDataInspection, XmlEncError> {
354 let parse_budget = XmlParseWorkBudget::from_resources(&policy.resources);
355 policy.validate()?;
356 let references = super::CipherReferenceContext::new(policy, None, backend, id_attributes)?;
357 validate_node_document_policy(node, policy.into(), &parse_budget, backend)?;
358 let (data, origins) = parse_encrypted_data_node_with_origins(
359 node,
360 policy.into(),
361 false,
362 id_attributes,
363 provider,
364 Some(&references),
365 Some(&parse_budget),
366 )?;
367 let bound = references.bind_document(node.document());
368 let recipient =
369 super::decrypt::select_encapsulation_recipient(&data, &bound, node.id(), &origins, policy)?;
370 Ok(EncryptedDataInspection { data, recipient })
371}
372
373pub fn parse_encrypted_data_template_node_with_policy(
380 node: Node<'_, '_>,
381 policy: &crate::policy::EncryptionPolicy,
382) -> Result<EncryptedData, XmlEncError> {
383 parse_encrypted_data_template_node_with_policy_and_backend(
384 node,
385 policy,
386 crate::XmlBackend::default(),
387 )
388}
389
390pub fn parse_encrypted_data_template_node_with_policy_and_backend(
392 node: Node<'_, '_>,
393 policy: &crate::policy::EncryptionPolicy,
394 backend: crate::XmlBackend,
395) -> Result<EncryptedData, XmlEncError> {
396 inspect_encrypted_data_template_node_with_context(
397 node,
398 policy,
399 backend,
400 crate::provider::default_provider(),
401 &[],
402 )
403 .map(|template| template.data)
404}
405
406pub struct EncryptedDataTemplate<'doc> {
410 data: EncryptedData,
411 encapsulation_values: Vec<Node<'doc, 'doc>>,
412}
413
414impl<'doc> EncryptedDataTemplate<'doc> {
415 pub fn data(&self) -> &EncryptedData {
417 &self.data
418 }
419
420 pub fn into_data(self) -> EncryptedData {
422 self.data
423 }
424
425 pub fn encapsulation_cipher_values(&self) -> &[Node<'doc, 'doc>] {
427 &self.encapsulation_values
428 }
429}
430
431pub fn inspect_encrypted_data_template_node_with_context<'doc>(
433 node: Node<'doc, 'doc>,
434 policy: &crate::policy::EncryptionPolicy,
435 backend: crate::XmlBackend,
436 provider: &dyn crate::provider::CryptoProvider,
437 id_attributes: &[crate::IdAttributeRegistration],
438) -> Result<EncryptedDataTemplate<'doc>, XmlEncError> {
439 let parse_budget = XmlParseWorkBudget::from_resources(&policy.resources);
440 policy.validate()?;
441 let policy = ParsingPolicy::from(policy);
442 validate_node_document_policy(node, policy, &parse_budget, backend)?;
443 let (data, _, encapsulation_values) = parse_encrypted_data_node_traced(
444 node,
445 policy,
446 true,
447 id_attributes,
448 provider,
449 None,
450 Some(&parse_budget),
451 )?;
452 Ok(EncryptedDataTemplate {
453 data,
454 encapsulation_values,
455 })
456}
457
458fn validate_node_document_policy(
459 node: Node<'_, '_>,
460 policy: ParsingPolicy<'_>,
461 parse_budget: &XmlParseWorkBudget,
462 backend: crate::XmlBackend,
463) -> Result<(), XmlEncError> {
464 parse_policy_document(node.document().input_text(), policy, parse_budget, backend)?;
465 Ok(())
466}
467
468fn parse_policy_document<'a>(
469 xml: &'a str,
470 policy: ParsingPolicy<'_>,
471 parse_budget: &XmlParseWorkBudget,
472 backend: crate::XmlBackend,
473) -> Result<Document<'a>, XmlEncError> {
474 let settings =
475 DocumentParseSettings::from_policy(policy.xml, policy.resources).with_backend(backend);
476 parse_borrowed_with_settings_and_budget(xml, settings, Some(parse_budget))
477 .map_err(|error| map_document_error(error, settings))
478}
479
480pub(super) fn parse_encrypted_data_node_with_origins(
481 node: Node<'_, '_>,
482 policy: ParsingPolicy<'_>,
483 allow_empty_cipher_values: bool,
484 registrations: &[crate::IdAttributeRegistration],
485 provider: &dyn crate::provider::CryptoProvider,
486 references: Option<&super::CipherReferenceContext<'_>>,
487 xml_parse: Option<&XmlParseWorkBudget>,
488) -> Result<(EncryptedData, Vec<Option<crate::NodeId>>), XmlEncError> {
489 parse_encrypted_data_node_traced(
490 node,
491 policy,
492 allow_empty_cipher_values,
493 registrations,
494 provider,
495 references,
496 xml_parse,
497 )
498 .map(|(data, origins, _)| (data, origins))
499}
500
501type TracedEncryptedData<'doc> = (
502 EncryptedData,
503 Vec<Option<crate::NodeId>>,
504 Vec<Node<'doc, 'doc>>,
505);
506
507fn parse_encrypted_data_node_traced<'doc>(
508 node: Node<'doc, 'doc>,
509 policy: ParsingPolicy<'_>,
510 allow_empty_cipher_values: bool,
511 registrations: &[crate::IdAttributeRegistration],
512 provider: &dyn crate::provider::CryptoProvider,
513 references: Option<&super::CipherReferenceContext<'_>>,
514 xml_parse: Option<&XmlParseWorkBudget>,
515) -> Result<TracedEncryptedData<'doc>, XmlEncError> {
516 require_element(node, XMLENC_NS, "EncryptedData")?;
517 let mut shared = SharedKeySourceParseBudget::new(policy.resources)?;
518 let mut budget = KeySourceParseBudget::new(policy, registrations, provider, &mut shared);
519 budget.references = references;
520 budget.xml_parse = xml_parse;
521 validate_encrypted_type_attributes(node, policy)?;
522 let mut children = element_children(node);
523 let encryption_method = parse_encryption_method_with_limit(
524 next_required(&mut children, "EncryptionMethod")?,
525 policy.resources.max_encryption_metadata_bytes,
526 )?;
527 if children
528 .peek()
529 .is_some_and(|child| child.has_tag_name((XMLENC_NS, "EncryptionMethod")))
530 {
531 return Err(XmlEncError::InvalidStructure(
532 "EncryptedData contains more than one direct EncryptionMethod".into(),
533 ));
534 }
535
536 let key_info_node = match children.peek() {
537 Some(child) if child.has_tag_name((XMLDSIG_NS, "KeyInfo")) => {
538 Some(next_required(&mut children, "KeyInfo")?)
539 }
540 _ => None,
541 };
542 if children
543 .peek()
544 .is_some_and(|child| child.has_tag_name((XMLDSIG_NS, "KeyInfo")))
545 {
546 return Err(XmlEncError::InvalidStructure(
547 "EncryptedData contains more than one direct KeyInfo".into(),
548 ));
549 }
550
551 let cipher_data = parse_cipher_data(
552 next_required(&mut children, "CipherData")?,
553 allow_empty_cipher_values
554 || encryption_method.algorithm == super::DataEncryptionAlgorithm::ChaCha20.uri(),
555 policy,
556 &mut budget.shared.transforms,
557 &mut budget.shared.retained_cipher_bytes,
558 )?;
559 consume_encryption_properties(&mut children);
560 if children.next().is_some() {
561 return Err(XmlEncError::InvalidStructure(
562 "EncryptedData has unexpected child after CipherData".into(),
563 ));
564 }
565
566 if let Some(references) = references {
569 references.validate_method(&encryption_method, true, provider)?;
570 references.preflight_cipher_data(&cipher_data)?;
571 }
572 let mut key_info = match key_info_node {
573 Some(key_info) => {
574 parse_key_info(key_info, policy, allow_empty_cipher_values, &mut budget, 0)?
575 }
576 None => ParsedKeyInfo {
577 encapsulation_methods: Vec::new(),
578 source_nodes: Vec::new(),
579 key_name: None,
580 encrypted_keys: Vec::new(),
581 derived_keys: Vec::new(),
582 agreement_methods: Vec::new(),
583 },
584 };
585 append_detached_keys(
586 node,
587 &mut key_info,
588 policy,
589 allow_empty_cipher_values,
590 &mut budget,
591 0,
592 )?;
593
594 let encrypted = EncryptedData {
595 encapsulation_methods: key_info.encapsulation_methods,
596 id: bounded_attribute(node, "Id", policy)?,
597 encrypted_type: parse_encrypted_data_type(node.attribute("Type")),
598 key_name: key_info.key_name,
599 encryption_method,
600 encrypted_keys: key_info.encrypted_keys,
601 derived_keys: key_info.derived_keys,
602 agreement_methods: key_info.agreement_methods,
603 cipher_data,
604 };
605 validate_encrypted_data_metadata_inner(&encrypted, policy, allow_empty_cipher_values)?;
606 Ok((encrypted, budget.origins, budget.encapsulation_values))
607}
608
609fn parse_key_info<'doc>(
610 node: Node<'doc, 'doc>,
611 policy: ParsingPolicy<'_>,
612 allow_empty_cipher_values: bool,
613 budget: &mut KeySourceParseBudget<'_, 'doc, '_>,
614 depth: usize,
615) -> Result<ParsedKeyInfo, XmlEncError> {
616 require_element(node, XMLDSIG_NS, "KeyInfo")?;
617 policy.resources.validate_key_info_reference_depth(depth)?;
618 let recipients = node
619 .children()
620 .filter(|child| child.has_tag_name((XMLENC_NS, "EncryptedKey")))
621 .count();
622 if recipients > policy.resources.max_encryption_recipients {
623 return Err(crate::policy::PolicyViolation::ResourceLimit {
624 resource: crate::policy::resource_name::ENCRYPTION_RECIPIENTS,
625 maximum: policy.resources.max_encryption_recipients,
626 actual: recipients,
627 }
628 .into());
629 }
630 if budget.ancestry.contains(&node.id()) {
631 return Err(XmlEncError::InvalidStructure(
632 "cyclic encryption KeyInfo reference".into(),
633 ));
634 }
635 budget.ancestry.push(node.id());
636 let mut key_name = None;
637 let mut source_nodes = Vec::new();
638 let mut encrypted_keys = Vec::new();
639 let mut derived_keys = Vec::new();
640 let mut agreement_methods = Vec::new();
641 let mut encapsulation_methods = Vec::new();
642 for child in node.children().filter(Node::is_element) {
643 if child.has_tag_name((
644 crate::key_establishment::ENCAPSULATION_NS,
645 "EncapsulationMechanism",
646 )) {
647 budget.charge()?;
648 let mechanism =
649 crate::key_establishment::parse_encapsulation(child, allow_empty_cipher_values)?;
650 policy
651 .key_establishment
652 .check_encapsulation(mechanism.algorithm)?;
653 let bytes = mechanism.ciphertext().len().div_ceil(3) * 4;
654 let maximum = policy.resources.max_xml_document_bytes;
655 let used = budget.shared.retained_cipher_bytes;
656 if used > maximum || bytes > maximum - used {
657 return Err(crate::policy::PolicyViolation::ResourceLimit {
658 resource: crate::policy::resource_name::AGGREGATE_ENCRYPTION_CIPHER_VALUE_BYTES,
659 maximum,
660 actual: used.saturating_add(bytes),
661 }
662 .into());
663 }
664 budget.shared.retained_cipher_bytes += bytes;
665 let info = parse_encapsulation_key_info(mechanism.key_info, budget, depth + 1)?;
666 if allow_empty_cipher_values && budget.encrypted_key_depth == 0 {
667 budget.encapsulation_values.push(mechanism.cipher_value);
668 }
669 encapsulation_methods.push(crate::key_establishment::EncapsulationMechanism {
670 algorithm: mechanism.algorithm,
671 key_info: info,
672 ciphertext: mechanism.ciphertext().to_vec(),
673 });
674 source_nodes.push(child.id());
675 } else if child.has_tag_name((XMLDSIG_NS, "KeyName")) {
676 if key_name.is_some() {
677 return Err(XmlEncError::InvalidStructure(
678 "KeyInfo contains more than one direct KeyName".into(),
679 ));
680 }
681 key_name = Some(parse_key_name(child, policy)?);
682 } else if child.has_tag_name((XMLENC_NS, "EncryptedKey")) {
683 budget.charge_recipient(policy.resources, encrypted_keys.len())?;
684 source_nodes.push(child.id());
685 encrypted_keys.push(parse_encrypted_key(
686 child,
687 policy,
688 allow_empty_cipher_values,
689 budget,
690 depth + 1,
691 )?);
692 } else if child.has_tag_name((XMLENC11_NS, "DerivedKey")) {
693 budget.charge()?;
694 source_nodes.push(child.id());
695 derived_keys.push(super::derived_key::parse(child, policy.resources)?);
696 } else if child.has_tag_name((XMLENC_NS, "AgreementMethod")) {
697 budget.charge()?;
698 agreement_methods.push(super::agreement::parse(
701 child,
702 policy.resources,
703 &mut budget.shared.key_info,
704 budget.provider,
705 )?);
706 } else if child.has_tag_name((XMLDSIG_NS, "RetrievalMethod")) {
707 if child.children().any(|node| {
711 node.is_text()
712 && node.text().is_some_and(|text| {
713 !text
714 .bytes()
715 .all(|byte| matches!(byte, b' ' | b'\t' | b'\r' | b'\n'))
716 })
717 }) {
718 return Err(XmlEncError::InvalidStructure(
719 "RetrievalMethod contains non-whitespace text".into(),
720 ));
721 }
722 let uri = child
727 .attribute("URI")
728 .ok_or(XmlEncError::MissingRequired("RetrievalMethod URI"))?;
729 validate_metadata_len(uri.len(), policy.resources.max_encryption_metadata_bytes)?;
730 let allowed = policy
731 .uris
732 .map_or(crate::xmldsig::UriTypeSet::SAME_DOCUMENT, |uris| {
733 uris.retrieval_methods
734 });
735 if !allowed.allows(uri) {
736 return Err(crate::policy::PolicyViolation::Algorithm {
737 operation: "encryption key retrieval URI",
738 algorithm: uri.to_owned(),
739 }
740 .into());
741 }
742 let mut transform_children = element_children(child);
743 let transforms = match transform_children.next() {
744 Some(container) if container.has_tag_name((XMLDSIG_NS, "Transforms")) => {
745 if transform_children.next().is_some()
746 || !container.children().any(|node| node.is_element())
747 {
748 return Err(XmlEncError::InvalidStructure(
749 "RetrievalMethod requires one nonempty ds:Transforms container".into(),
750 ));
751 }
752 crate::xmldsig::transforms::parse_reference_transforms_with_budget(
753 container,
754 XMLDSIG_NS,
755 &mut budget.shared.transforms,
756 )?
757 }
758 Some(_) => {
759 return Err(XmlEncError::InvalidStructure(
760 "unexpected RetrievalMethod child".into(),
761 ));
762 }
763 None => Vec::new(),
764 };
765 let declared_type = child.attribute("Type");
766 let declared_type = declared_type
767 .map(|kind| {
768 validate_metadata_len(
769 kind.len(),
770 policy.resources.max_encryption_metadata_bytes,
771 )?;
772 RetrievedKeyType::parse(kind)
773 })
774 .transpose()?;
775 if !uri.is_empty() && !uri.starts_with('#') || !transforms.is_empty() {
776 budget.charge()?;
777 match parse_processed_key_reference(
778 child,
779 uri,
780 &transforms,
781 declared_type,
782 allow_empty_cipher_values,
783 budget,
784 depth + 1,
785 )? {
786 RetrievedKey::Encrypted(key) => {
787 if encrypted_keys.len() >= policy.resources.max_encryption_recipients {
788 return Err(crate::policy::PolicyViolation::ResourceLimit {
789 resource: crate::policy::resource_name::ENCRYPTION_RECIPIENTS,
790 maximum: policy.resources.max_encryption_recipients,
791 actual: encrypted_keys.len() + 1,
792 }
793 .into());
794 }
795 encrypted_keys.push(key);
796 }
797 RetrievedKey::Derived(key) => derived_keys.push(key),
798 }
799 continue;
800 }
801 let target = budget.referenced_node(child, uri)?;
802 if let Some(kind) = declared_type {
803 kind.validate_target(target)?;
804 }
805 if target.has_tag_name((XMLENC11_NS, "DerivedKey")) {
806 budget.charge()?;
807 source_nodes.push(target.id());
808 derived_keys.push(super::derived_key::parse(target, policy.resources)?);
809 continue;
810 }
811 budget.charge_recipient(policy.resources, encrypted_keys.len())?;
812 source_nodes.push(target.id());
813 encrypted_keys.push(parse_encrypted_key(
814 target,
815 policy,
816 allow_empty_cipher_values,
817 budget,
818 depth + 1,
819 )?);
820 } else if child.has_tag_name((crate::xmldsig::parse::XMLDSIG11_NS, "KeyInfoReference")) {
821 let uri = child
826 .attribute("URI")
827 .ok_or(XmlEncError::MissingRequired("KeyInfoReference URI"))?;
828 validate_metadata_len(uri.len(), policy.resources.max_encryption_metadata_bytes)?;
829 if child.children().any(|node| {
830 node.is_element()
831 || node.is_text()
832 && node.text().is_some_and(|text| {
833 !text
834 .bytes()
835 .all(|byte| matches!(byte, b' ' | b'\t' | b'\r' | b'\n'))
836 })
837 }) {
838 return Err(XmlEncError::InvalidStructure(
839 "KeyInfoReference must be empty".into(),
840 ));
841 }
842 let allowed = policy
843 .uris
844 .map_or(crate::xmldsig::UriTypeSet::SAME_DOCUMENT, |uris| {
845 uris.key_info_references
846 });
847 if !allowed.allows(uri) {
848 return Err(crate::policy::PolicyViolation::Algorithm {
849 operation: "encryption KeyInfo reference URI",
850 algorithm: uri.to_owned(),
851 }
852 .into());
853 }
854 budget.charge()?;
855 let target = budget.referenced_node(child, uri)?;
856 let info =
857 parse_key_info(target, policy, allow_empty_cipher_values, budget, depth + 1)?;
858 if let Some(name) = info.key_name {
859 if key_name.is_some() {
860 return Err(XmlEncError::InvalidStructure(
861 "indirect KeyInfo contains conflicting KeyName".into(),
862 ));
863 }
864 key_name = Some(name);
865 }
866 if encrypted_keys.len() + info.encrypted_keys.len()
867 > policy.resources.max_encryption_recipients
868 {
869 return Err(crate::policy::PolicyViolation::ResourceLimit {
870 resource: crate::policy::resource_name::ENCRYPTION_RECIPIENTS,
871 maximum: policy.resources.max_encryption_recipients,
872 actual: encrypted_keys.len() + info.encrypted_keys.len(),
873 }
874 .into());
875 }
876 encrypted_keys.extend(info.encrypted_keys);
877 source_nodes.extend(info.source_nodes);
878 derived_keys.extend(info.derived_keys);
879 agreement_methods.extend(info.agreement_methods);
880 encapsulation_methods.extend(info.encapsulation_methods);
881 }
882 }
883 budget.ancestry.pop();
884 Ok(ParsedKeyInfo {
885 encapsulation_methods,
886 source_nodes,
887 key_name,
888 encrypted_keys,
889 derived_keys,
890 agreement_methods,
891 })
892}
893
894fn parse_encapsulation_key_info<'doc>(
895 node: Node<'doc, 'doc>,
896 budget: &mut KeySourceParseBudget<'_, 'doc, '_>,
897 depth: usize,
898) -> Result<crate::xmldsig::parse::KeyInfo, XmlEncError> {
899 use crate::xmldsig::parse::KeyInfoSource;
900 budget
901 .policy
902 .resources
903 .validate_key_info_reference_depth(depth)?;
904 if budget.ancestry.contains(&node.id()) {
905 return Err(XmlEncError::InvalidStructure(
906 "cyclic encryption KeyInfo reference".into(),
907 ));
908 }
909 budget.ancestry.push(node.id());
910 let mut info = budget
911 .shared
912 .key_info
913 .parse_with_provider_and_document_base(
914 node,
915 budget.provider,
916 budget.document_base.as_deref(),
917 )
918 .map_err(super::agreement::map_key_info_error)?;
919 if info
920 .sources
921 .iter()
922 .any(|source| matches!(source, KeyInfoSource::KeyInfoReference { .. }))
923 {
924 let mut sources = Vec::new();
925 for source in std::mem::take(&mut info.sources) {
926 if let KeyInfoSource::KeyInfoReference { uri } = source {
927 let allowed = budget
931 .policy
932 .uris
933 .map_or(crate::xmldsig::UriTypeSet::SAME_DOCUMENT, |uris| {
934 uris.key_info_references
935 });
936 if !allowed.allows(&uri) {
937 return Err(crate::policy::PolicyViolation::Uri {
938 operation: "encryption KeyInfoReference",
939 reason: "URI class is disabled",
940 }
941 .into());
942 }
943 budget.charge()?;
944 budget
945 .policy
946 .resources
947 .validate_key_info_reference_depth(depth + 1)?;
948 let referenced = if uri.is_empty() || uri.starts_with('#') {
949 let target = budget.referenced_node(node, &uri)?;
950 parse_encapsulation_key_info(target, budget, depth + 1)?
951 } else {
952 let resource = uri
953 .split_once('#')
954 .map_or(uri.as_str(), |(resource, _)| resource);
955 let fragment = &uri[resource.len()..];
956 with_processed_key_reference(
957 node,
958 resource,
959 allowed,
960 &[],
961 budget,
962 |root, child| {
963 let target = if fragment.is_empty() || fragment == "#" {
964 root
965 } else {
966 child.referenced_node(root, fragment)?
967 };
968 require_element(target, XMLDSIG_NS, "KeyInfo")?;
970 parse_encapsulation_key_info(target, child, depth + 1)
971 },
972 )?
973 };
974 sources.extend(referenced.sources);
975 } else {
976 sources.push(source);
977 }
978 }
979 info.sources = sources;
980 }
981 budget.ancestry.pop();
982 Ok(info)
983}
984
985enum RetrievedKey {
986 Encrypted(EncryptedKey),
987 Derived(super::DerivedKey),
988}
989
990#[derive(Clone, Copy)]
991enum RetrievedKeyType {
992 Encrypted,
993 Derived,
994}
995
996impl RetrievedKeyType {
997 fn parse(uri: &str) -> Result<Self, XmlEncError> {
998 match uri {
1004 "http://www.w3.org/2001/04/xmlenc#EncryptedKey" => Ok(Self::Encrypted),
1005 "http://www.w3.org/2009/xmlenc11#DerivedKey"
1006 | "http://www.w3.org/2001/04/xmlenc#DerivedKey" => Ok(Self::Derived),
1007 _ => Err(XmlEncError::InvalidStructure(
1008 "unsupported encryption RetrievalMethod Type".into(),
1009 )),
1010 }
1011 }
1012
1013 fn validate_target(self, node: Node<'_, '_>) -> Result<(), XmlEncError> {
1014 let (namespace, tag) = match self {
1015 Self::Encrypted => (XMLENC_NS, "EncryptedKey"),
1016 Self::Derived => (XMLENC11_NS, "DerivedKey"),
1017 };
1018 if node.has_tag_name((namespace, tag)) {
1019 Ok(())
1020 } else {
1021 Err(XmlEncError::InvalidStructure(format!(
1022 "RetrievalMethod Type disagrees with {tag} target"
1023 )))
1024 }
1025 }
1026}
1027
1028fn parse_processed_key_reference(
1029 source: Node<'_, '_>,
1030 uri: &str,
1031 transforms: &[crate::xmldsig::transforms::Transform],
1032 declared_type: Option<RetrievedKeyType>,
1033 allow_empty: bool,
1034 budget: &mut KeySourceParseBudget<'_, '_, '_>,
1035 depth: usize,
1036) -> Result<RetrievedKey, XmlEncError> {
1037 budget
1038 .policy
1039 .resources
1040 .validate_key_info_reference_depth(depth)?;
1041 let allowed = budget
1042 .policy
1043 .uris
1044 .map_or(crate::xmldsig::UriTypeSet::SAME_DOCUMENT, |uris| {
1045 uris.retrieval_methods
1046 });
1047 with_processed_key_reference(
1048 source,
1049 uri,
1050 allowed,
1051 transforms,
1052 budget,
1053 |target, child_budget| {
1054 if let Some(kind) = declared_type {
1055 kind.validate_target(target)?;
1056 }
1057 if target.has_tag_name((XMLENC11_NS, "DerivedKey")) {
1058 return Ok(RetrievedKey::Derived(super::derived_key::parse(
1059 target,
1060 child_budget.policy.resources,
1061 )?));
1062 }
1063 let mut key = parse_encrypted_key(
1064 target,
1065 child_budget.policy,
1066 allow_empty,
1067 child_budget,
1068 depth,
1069 )?;
1070 let context = child_budget
1071 .references
1072 .expect("processed reference context");
1073 let parse = child_budget
1074 .xml_parse
1075 .expect("processed reference parse budget");
1076 let bound = context
1077 .bind_document_with_base(target.document(), child_budget.document_base.as_deref());
1078 let mut origins = child_budget.origins.iter();
1079 super::decrypt::resolve_nested_cipher_references(
1080 core::slice::from_mut(&mut key),
1081 target.document(),
1082 &mut origins,
1083 &bound,
1084 parse,
1085 )?;
1086 if origins.next().is_some() {
1087 return Err(XmlEncError::OperationPlan(
1088 "unused processed key origin".into(),
1089 ));
1090 }
1091 Ok(RetrievedKey::Encrypted(key))
1092 },
1093 )
1094}
1095
1096fn with_processed_key_reference<T>(
1097 source: Node<'_, '_>,
1098 uri: &str,
1099 allowed_uris: crate::xmldsig::UriTypeSet,
1100 transforms: &[crate::xmldsig::transforms::Transform],
1101 budget: &mut KeySourceParseBudget<'_, '_, '_>,
1102 consume: impl for<'doc> FnOnce(
1103 Node<'doc, 'doc>,
1104 &mut KeySourceParseBudget<'_, 'doc, '_>,
1105 ) -> Result<T, XmlEncError>,
1106) -> Result<T, XmlEncError> {
1107 use sha2::Digest as _;
1108 let policy = budget.policy;
1109 let context = budget.references.ok_or_else(|| {
1110 XmlEncError::InvalidStructure(
1111 "processed key retrieval requires an operation resource context".into(),
1112 )
1113 })?;
1114 let parse = budget
1115 .xml_parse
1116 .ok_or_else(|| XmlEncError::OperationPlan("missing key resource parse budget".into()))?;
1117 let (bytes, base) = context.resolve_key_reference(
1118 source,
1119 uri,
1120 allowed_uris,
1121 transforms,
1122 budget.document_base.as_deref(),
1123 parse,
1124 )?;
1125 policy.resources.validate_xml_document_len(bytes.len())?;
1126 let fingerprint: [u8; 32] = sha2::Sha256::digest(&bytes).into();
1129 if budget
1130 .shared
1131 .reference_ancestry
1132 .iter()
1133 .any(|(identity, hash)| identity == &base && hash == &fingerprint)
1134 {
1135 return Err(XmlEncError::InvalidStructure(
1136 "cyclic processed encryption key reference".into(),
1137 ));
1138 }
1139 budget
1140 .shared
1141 .reference_ancestry
1142 .push((base.clone(), fingerprint));
1143 let settings = DocumentParseSettings::from_policy(policy.xml, policy.resources)
1144 .with_backend(context.backend());
1145 let xml = crate::document::decode_xml_with_budget(
1146 &bytes,
1147 policy.resources.max_xml_document_bytes,
1148 Some(parse),
1149 )
1150 .map_err(|error| map_document_error(error, settings))?;
1151 let document = parse_policy_document(&xml, policy, parse, context.backend())?;
1152 let mut child_budget = KeySourceParseBudget::new(
1153 budget.policy,
1154 budget.registrations,
1155 budget.provider,
1156 &mut *budget.shared,
1157 );
1158 child_budget.references = Some(context);
1159 child_budget.xml_parse = Some(parse);
1160 child_budget.document_base = base;
1161 let result = consume(document.root_element(), &mut child_budget)?;
1162 budget
1163 .origins
1164 .extend(child_budget.origins.iter().map(|_| None));
1165 budget.shared.reference_ancestry.pop();
1166 Ok(result)
1167}
1168
1169fn append_detached_keys<'doc>(
1170 node: Node<'doc, 'doc>,
1171 info: &mut ParsedKeyInfo,
1172 policy: ParsingPolicy<'_>,
1173 allow_empty_cipher_values: bool,
1174 budget: &mut KeySourceParseBudget<'_, 'doc, '_>,
1175 depth: usize,
1176) -> Result<(), XmlEncError> {
1177 if budget.detached.is_none() {
1182 let mut candidates = Vec::new();
1183 let maximum = policy.resources.effective_xml_nodes() as usize;
1184 for (index, candidate) in node.document().descendants().enumerate() {
1185 if index >= maximum {
1188 return Err(crate::policy::PolicyViolation::ResourceLimit {
1189 resource: crate::policy::resource_name::XML_NODES,
1190 maximum,
1191 actual: index + 1,
1192 }
1193 .into());
1194 }
1195 if candidate.has_tag_name((XMLENC_NS, "EncryptedKey"))
1196 || candidate.has_tag_name((XMLENC11_NS, "DerivedKey"))
1197 {
1198 candidates.push(candidate.id());
1199 }
1200 }
1201 budget.detached = Some(candidates);
1202 }
1203 let count = budget
1204 .detached
1205 .as_ref()
1206 .expect("inventory initialized")
1207 .len();
1208 for index in 0..count {
1209 let id = budget.detached.as_ref().expect("inventory initialized")[index];
1210 let candidate = node
1211 .document()
1212 .get_node(id)
1213 .expect("inventory belongs to this document");
1214 let derived = candidate.has_tag_name((XMLENC11_NS, "DerivedKey"));
1215 let named = info.key_name.as_deref().is_some_and(|expected| {
1216 candidate
1217 .children()
1218 .find(|child| {
1219 child.has_tag_name(if derived {
1220 (XMLENC11_NS, "DerivedKeyName")
1221 } else {
1222 (XMLENC_NS, "CarriedKeyName")
1223 })
1224 })
1225 .is_some_and(|label| {
1226 !label.children().any(|child| child.is_element())
1227 && label
1228 .children()
1229 .filter_map(|child| child.text())
1230 .flat_map(str::bytes)
1231 .eq(expected.bytes())
1232 })
1233 });
1234 let mut referenced = false;
1235 for list in candidate
1236 .children()
1237 .filter(|child| child.has_tag_name((XMLENC_NS, "ReferenceList")))
1238 {
1239 let data_target = node.has_tag_name((XMLENC_NS, "EncryptedData"));
1240 for child in list.children() {
1244 if !child.has_tag_name((
1245 XMLENC_NS,
1246 if data_target {
1247 "DataReference"
1248 } else {
1249 "KeyReference"
1250 },
1251 )) {
1252 continue;
1253 }
1254 let Some(uri) = child.attribute("URI") else {
1255 continue;
1256 };
1257 if !uri.is_empty() && !uri.starts_with('#') {
1258 continue;
1259 }
1260 if uri.len() > policy.resources.max_encryption_metadata_bytes {
1261 continue;
1262 }
1263 if budget
1264 .resolver(node)
1265 .same_document_reference_targets(uri, node.id())
1266 {
1267 referenced = true;
1268 break;
1269 }
1270 }
1271 if referenced {
1272 break;
1273 }
1274 }
1275 if !named && !referenced {
1276 continue;
1277 }
1278 for list in candidate
1281 .children()
1282 .filter(|child| child.has_tag_name((XMLENC_NS, "ReferenceList")))
1283 {
1284 visit_reference_list(list, policy.resources, |_, uri| {
1285 if uri.is_empty() || uri.starts_with('#') {
1286 budget
1287 .resolver(candidate)
1288 .node_for_same_document_reference(uri)?;
1289 }
1290 Ok(())
1291 })?;
1292 }
1293 if budget.ancestry.contains(&id) {
1294 return Err(XmlEncError::InvalidStructure(
1295 "cyclic detached EncryptedKey association".into(),
1296 ));
1297 }
1298 if info.source_nodes.contains(&id) {
1299 continue;
1300 }
1301 if derived {
1302 budget.charge()?;
1303 info.source_nodes.push(id);
1304 info.derived_keys
1305 .push(super::derived_key::parse(candidate, policy.resources)?);
1306 continue;
1307 }
1308 budget.charge_recipient(policy.resources, info.encrypted_keys.len())?;
1309 info.source_nodes.push(id);
1310 info.encrypted_keys.push(parse_encrypted_key(
1311 candidate,
1312 policy,
1313 allow_empty_cipher_values,
1314 budget,
1315 depth + 1,
1316 )?);
1317 }
1318 Ok(())
1319}
1320
1321fn parse_encrypted_key<'doc>(
1322 node: Node<'doc, 'doc>,
1323 policy: ParsingPolicy<'_>,
1324 allow_empty_cipher_values: bool,
1325 budget: &mut KeySourceParseBudget<'_, 'doc, '_>,
1326 depth: usize,
1327) -> Result<EncryptedKey, XmlEncError> {
1328 require_element(node, XMLENC_NS, "EncryptedKey")?;
1329 policy.resources.validate_key_info_reference_depth(depth)?;
1333 if budget.ancestry.contains(&node.id()) {
1334 return Err(XmlEncError::InvalidStructure(
1335 "cyclic EncryptedKey reference".into(),
1336 ));
1337 }
1338 budget.ancestry.push(node.id());
1339 budget.origins.push(Some(node.id()));
1340 budget.encrypted_key_depth += 1;
1341 validate_encrypted_type_attributes(node, policy)?;
1342 let mut children = element_children(node);
1343 let encryption_method = parse_encryption_method_with_limit(
1344 next_required(&mut children, "EncryptionMethod")?,
1345 policy.resources.max_encryption_metadata_bytes,
1346 )?;
1347 if children
1348 .peek()
1349 .is_some_and(|child| child.has_tag_name((XMLENC_NS, "EncryptionMethod")))
1350 {
1351 return Err(XmlEncError::InvalidStructure(
1352 "EncryptedKey contains more than one direct EncryptionMethod".into(),
1353 ));
1354 }
1355 let key_info_node = if children
1356 .peek()
1357 .is_some_and(|child| child.has_tag_name((XMLDSIG_NS, "KeyInfo")))
1358 {
1359 Some(next_required(&mut children, "KeyInfo")?)
1360 } else {
1361 None
1362 };
1363 if children
1364 .peek()
1365 .is_some_and(|child| child.has_tag_name((XMLDSIG_NS, "KeyInfo")))
1366 {
1367 return Err(XmlEncError::InvalidStructure(
1368 "EncryptedKey contains more than one direct KeyInfo".into(),
1369 ));
1370 }
1371 let cipher_data = parse_cipher_data(
1372 next_required(&mut children, "CipherData")?,
1373 allow_empty_cipher_values,
1374 policy,
1375 &mut budget.shared.transforms,
1376 &mut budget.shared.retained_cipher_bytes,
1377 )?;
1378 consume_encryption_properties(&mut children);
1379 let reference_list = if children
1380 .peek()
1381 .is_some_and(|child| child.has_tag_name((XMLENC_NS, "ReferenceList")))
1382 {
1383 Some(parse_reference_list(
1384 next_required(&mut children, "ReferenceList")?,
1385 policy,
1386 )?)
1387 } else {
1388 None
1389 };
1390 let carried_key_name = if children
1391 .peek()
1392 .is_some_and(|child| child.has_tag_name((XMLENC_NS, "CarriedKeyName")))
1393 {
1394 Some(parse_carried_key_name(
1395 next_required(&mut children, "CarriedKeyName")?,
1396 policy,
1397 )?)
1398 } else {
1399 None
1400 };
1401 if children.next().is_some() {
1402 return Err(XmlEncError::InvalidStructure(
1403 "EncryptedKey has unexpected child after CipherData".into(),
1404 ));
1405 }
1406 if let Some(references) = budget.references {
1407 if key_info_node.is_some_and(|info| {
1410 info.descendants().any(|child| {
1411 child.has_tag_name((XMLDSIG_NS, "RetrievalMethod"))
1412 || child.has_tag_name((crate::xmldsig::parse::XMLDSIG11_NS, "KeyInfoReference"))
1413 })
1414 }) {
1415 references.validate_method(&encryption_method, false, budget.provider)?;
1416 }
1417 references.preflight_cipher_data(&cipher_data)?;
1418 }
1419 let mut key_info = match key_info_node {
1420 Some(key_info) => Some(parse_key_info(
1421 key_info,
1422 policy,
1423 allow_empty_cipher_values,
1424 budget,
1425 depth,
1426 )?),
1427 None => None,
1428 };
1429 let info = key_info.get_or_insert_with(|| ParsedKeyInfo {
1430 encapsulation_methods: Vec::new(),
1431 source_nodes: Vec::new(),
1432 key_name: None,
1433 encrypted_keys: Vec::new(),
1434 derived_keys: Vec::new(),
1435 agreement_methods: Vec::new(),
1436 });
1437 append_detached_keys(node, info, policy, allow_empty_cipher_values, budget, depth)?;
1438 budget.ancestry.pop();
1439 budget.encrypted_key_depth -= 1;
1440 Ok(EncryptedKey {
1441 sources: key_info
1442 .as_mut()
1443 .map(|info| super::EncryptionKeySources {
1444 encapsulation_methods: core::mem::take(&mut info.encapsulation_methods),
1445 encrypted_keys: core::mem::take(&mut info.encrypted_keys),
1446 derived_keys: core::mem::take(&mut info.derived_keys),
1447 agreement_methods: core::mem::take(&mut info.agreement_methods),
1448 })
1449 .unwrap_or_default(),
1450 id: bounded_attribute(node, "Id", policy)?,
1451 recipient: bounded_attribute(node, "Recipient", policy)?,
1452 key_name: key_info.and_then(|info| info.key_name),
1453 encryption_method,
1454 cipher_data,
1455 reference_list,
1456 carried_key_name,
1457 })
1458}
1459
1460fn parse_carried_key_name(
1461 node: Node<'_, '_>,
1462 policy: ParsingPolicy<'_>,
1463) -> Result<String, XmlEncError> {
1464 require_element(node, XMLENC_NS, "CarriedKeyName")?;
1465 let value = bounded_simple_text(node, "CarriedKeyName", policy)?;
1466 if value.is_empty() {
1467 return Err(XmlEncError::InvalidStructure(
1468 "CarriedKeyName is empty".into(),
1469 ));
1470 }
1471 Ok(value)
1472}
1473
1474fn parse_key_name(node: Node<'_, '_>, policy: ParsingPolicy<'_>) -> Result<String, XmlEncError> {
1475 let value = bounded_simple_text(node, "KeyName", policy)?;
1476 if value.is_empty() {
1477 return Err(XmlEncError::InvalidStructure("KeyName is empty".into()));
1478 }
1479 Ok(value)
1480}
1481
1482fn parse_reference_list(
1483 node: Node<'_, '_>,
1484 policy: ParsingPolicy<'_>,
1485) -> Result<ReferenceList, XmlEncError> {
1486 parse_reference_list_with_resources(node, policy.resources)
1487}
1488
1489pub(super) fn parse_reference_list_with_resources(
1490 node: Node<'_, '_>,
1491 resources: &crate::policy::ResourcePolicy,
1492) -> Result<ReferenceList, XmlEncError> {
1493 require_element(node, XMLENC_NS, "ReferenceList")?;
1494 let mut data_references = Vec::new();
1495 let mut key_references = Vec::new();
1496 visit_reference_list(node, resources, |data_reference, uri| {
1497 if data_reference {
1498 data_references.push(uri.to_owned());
1499 } else {
1500 key_references.push(uri.to_owned());
1501 }
1502 Ok(())
1503 })?;
1504 Ok(ReferenceList {
1505 data_references,
1506 key_references,
1507 })
1508}
1509
1510fn visit_reference_list(
1511 node: Node<'_, '_>,
1512 resources: &crate::policy::ResourcePolicy,
1513 mut visit: impl FnMut(bool, &str) -> Result<(), XmlEncError>,
1514) -> Result<(), XmlEncError> {
1515 require_element(node, XMLENC_NS, "ReferenceList")?;
1516 let mut count = 0;
1517 for child in node.children() {
1522 if child.is_text()
1523 && child.text().is_some_and(|text| {
1524 !text
1525 .bytes()
1526 .all(|byte| matches!(byte, b' ' | b'\t' | b'\r' | b'\n'))
1527 })
1528 {
1529 return Err(XmlEncError::InvalidStructure(
1530 "ReferenceList contains character data".into(),
1531 ));
1532 }
1533 if !child.is_element() {
1534 continue;
1535 }
1536 let data_reference = match (child.tag_name().namespace(), child.tag_name().name()) {
1537 (Some(XMLENC_NS), "DataReference") => true,
1538 (Some(XMLENC_NS), "KeyReference") => false,
1539 _ => {
1540 return Err(XmlEncError::InvalidStructure(format!(
1541 "unsupported ReferenceList child {}",
1542 child.tag_name().name(),
1543 )));
1544 }
1545 };
1546 count += 1;
1547 if count > resources.max_references {
1548 return Err(crate::policy::PolicyViolation::ResourceLimit {
1549 resource: crate::policy::resource_name::ENCRYPTION_REFERENCES,
1550 maximum: resources.max_references,
1551 actual: count,
1552 }
1553 .into());
1554 }
1555 let uri = child
1556 .attribute("URI")
1557 .ok_or(XmlEncError::MissingRequired("Reference URI attribute"))?;
1558 validate_metadata_len(uri.len(), resources.max_encryption_metadata_bytes)?;
1559 visit(data_reference, uri)?;
1560 }
1561 if count == 0 {
1562 return Err(XmlEncError::InvalidStructure(
1563 "ReferenceList must contain at least one reference".into(),
1564 ));
1565 }
1566 Ok(())
1567}
1568
1569fn consume_encryption_properties<'a, I>(children: &mut std::iter::Peekable<I>)
1570where
1571 I: Iterator<Item = Node<'a, 'a>>,
1572{
1573 if children
1574 .peek()
1575 .is_some_and(|child| child.has_tag_name((XMLENC_NS, "EncryptionProperties")))
1576 {
1577 let _ = children.next();
1578 }
1579}
1580
1581#[cfg(test)]
1582fn parse_encryption_method(node: Node<'_, '_>) -> Result<EncryptionMethod, XmlEncError> {
1583 parse_encryption_method_with_limit(node, crate::hard_limits::ENCRYPTION_METADATA_BYTE_CEILING)
1584}
1585
1586fn parse_encryption_method_with_limit(
1587 node: Node<'_, '_>,
1588 metadata_limit: usize,
1589) -> Result<EncryptionMethod, XmlEncError> {
1590 require_element(node, XMLENC_NS, "EncryptionMethod")?;
1591 let algorithm = node
1592 .attribute("Algorithm")
1593 .ok_or(XmlEncError::MissingRequired(
1594 "EncryptionMethod Algorithm attribute",
1595 ))?;
1596 validate_metadata_len(algorithm.len(), metadata_limit)?;
1597 let algorithm = algorithm.to_owned();
1598
1599 let mut oaep_digest = None;
1600 let mut mgf_algorithm = None;
1601 let mut oaep_params = None;
1602 let mut key_size_bits = None;
1603 let mut chacha = None;
1604 for child in node.children().filter(Node::is_element) {
1605 match (child.tag_name().namespace(), child.tag_name().name()) {
1606 (Some(XMLENC_NS), "KeySize")
1607 if key_size_bits.is_none()
1608 && oaep_params.is_none()
1609 && oaep_digest.is_none()
1610 && mgf_algorithm.is_none()
1611 && chacha.is_none() =>
1612 {
1613 key_size_bits = Some(parse_key_size(child, metadata_limit)?);
1614 }
1615 (Some(XMLENC_NS), "OAEPparams") if oaep_params.is_none() => {
1616 oaep_params = Some(decode_bounded_base64_text(
1617 child,
1618 "OAEPparams",
1619 metadata_limit,
1620 )?);
1621 }
1622 (Some(XMLDSIG_NS), "DigestMethod") if oaep_digest.is_none() => {
1623 let digest = child
1624 .attribute("Algorithm")
1625 .ok_or(XmlEncError::MissingRequired(
1626 "DigestMethod Algorithm attribute",
1627 ))?;
1628 validate_metadata_len(digest.len(), metadata_limit)?;
1629 oaep_digest = Some(digest.to_owned());
1630 }
1631 (Some(XMLENC11_NS), "MGF") if mgf_algorithm.is_none() => {
1632 let mgf = child
1633 .attribute("Algorithm")
1634 .ok_or(XmlEncError::MissingRequired("MGF Algorithm attribute"))?;
1635 validate_metadata_len(mgf.len(), metadata_limit)?;
1636 mgf_algorithm = Some(mgf.to_owned());
1637 }
1638 (
1639 None | Some("http://www.w3.org/2021/04/xmldsig-more#"),
1640 name @ ("Nonce" | "Counter" | "AAD"),
1641 ) => {
1642 let parameters = chacha.get_or_insert_with(super::ChaChaParameters::default);
1647 match name {
1648 "Nonce" if parameters.nonce.is_none() => {
1649 parameters.nonce = Some(parse_chacha_hex(child, "Nonce", metadata_limit)?);
1650 }
1651 "Counter" if parameters.counter.is_none() => {
1652 parameters.counter =
1653 Some(parse_chacha_hex(child, "Counter", metadata_limit)?);
1654 }
1655 "AAD" if parameters.aad.is_none() => {
1656 parameters.aad = Some(bounded_simple_text_with_limit(
1657 child,
1658 "AAD",
1659 metadata_limit,
1660 )?);
1661 }
1662 _ => {
1663 return Err(XmlEncError::InvalidStructure(format!(
1664 "duplicate ChaCha {name}"
1665 )));
1666 }
1667 }
1668 }
1669 _ => {
1670 return Err(XmlEncError::InvalidStructure(format!(
1671 "unsupported EncryptionMethod child {}",
1672 child.tag_name().name()
1673 )));
1674 }
1675 }
1676 }
1677
1678 if chacha.is_none() && algorithm == super::DataEncryptionAlgorithm::ChaCha20Poly1305.uri() {
1679 chacha = Some(super::ChaChaParameters::default());
1680 }
1681 let method = EncryptionMethod {
1682 algorithm,
1683 key_size_bits,
1684 oaep_digest,
1685 mgf_algorithm,
1686 oaep_params,
1687 chacha,
1688 };
1689 method.validate_structure()?;
1690 Ok(method)
1691}
1692
1693fn parse_chacha_hex<const N: usize>(
1694 node: Node<'_, '_>,
1695 field: &'static str,
1696 metadata_limit: usize,
1697) -> Result<[u8; N], XmlEncError> {
1698 let value = bounded_simple_text_with_limit(node, field, metadata_limit)?;
1699 let value = value.trim_matches([' ', '\t', '\r', '\n']);
1700 if value.len() != N * 2 {
1701 return Err(XmlEncError::InvalidStructure(format!(
1702 "ChaCha {field} requires {} hexadecimal digits",
1703 N * 2
1704 )));
1705 }
1706 let mut result = [0; N];
1707 for (byte, pair) in result.iter_mut().zip(value.as_bytes().as_chunks::<2>().0) {
1708 let digit = |value: u8| match value {
1709 b'0'..=b'9' => Some(value - b'0'),
1710 b'a'..=b'f' => Some(value - b'a' + 10),
1711 b'A'..=b'F' => Some(value - b'A' + 10),
1712 _ => None,
1713 };
1714 *byte = digit(pair[0])
1715 .zip(digit(pair[1]))
1716 .map(|(high, low)| high * 16 + low)
1717 .ok_or_else(|| {
1718 XmlEncError::InvalidStructure(format!("invalid ChaCha {field} hexBinary"))
1719 })?;
1720 }
1721 Ok(result)
1722}
1723
1724fn parse_key_size(node: Node<'_, '_>, metadata_limit: usize) -> Result<usize, XmlEncError> {
1725 let value = bounded_simple_text_with_limit(node, "KeySize", metadata_limit)?;
1726 let value = value.trim();
1727 let bits = value
1728 .parse::<usize>()
1729 .map_err(|_| XmlEncError::InvalidStructure("KeySize must be a positive integer".into()))?;
1730 if bits == 0 {
1731 return Err(XmlEncError::InvalidStructure(
1732 "KeySize must be a positive integer".into(),
1733 ));
1734 }
1735 Ok(bits)
1736}
1737
1738fn parse_cipher_data(
1739 node: Node<'_, '_>,
1740 allow_empty: bool,
1741 policy: ParsingPolicy<'_>,
1742 transform_budget: &mut crate::xmldsig::transforms::XPathSignatureParseBudget,
1743 retained_cipher_bytes: &mut usize,
1744) -> Result<CipherData, XmlEncError> {
1745 require_element(node, XMLENC_NS, "CipherData")?;
1746 let mut children = element_children(node);
1747 let value = next_required(&mut children, "CipherValue")?;
1748 if children.next().is_some() {
1749 return Err(XmlEncError::InvalidStructure(
1750 "CipherData must contain exactly one CipherValue or CipherReference".into(),
1751 ));
1752 }
1753 if value.has_tag_name((XMLENC_NS, "CipherReference")) {
1756 let (uri, transforms) =
1757 super::cipher_reference::parse_reference(value, policy.resources, transform_budget)?;
1758 return Ok(CipherData::Reference {
1759 uri: uri.to_owned(),
1760 transforms,
1761 });
1762 }
1763 require_element(value, XMLENC_NS, "CipherValue")?;
1764 let payload = crate::xmldsig::whitespace::XmlBase64Payload::bounded(
1768 value,
1769 usize::MAX,
1770 MAX_CIPHER_VALUE_BASE64_LEN,
1771 )
1772 .map_err(|reason| {
1773 if reason == "unexpected nested element" {
1774 XmlEncError::InvalidStructure("CipherValue must not contain element children".into())
1775 } else {
1776 XmlEncError::Base64(format!("CipherValue: {reason}"))
1777 }
1778 })?;
1779 if payload.normalized_len == 0 && !allow_empty {
1780 return Err(XmlEncError::Base64("CipherValue is empty".into()));
1781 }
1782 let maximum = policy.resources.max_xml_document_bytes;
1783 if payload.normalized_len > maximum - *retained_cipher_bytes {
1784 return Err(crate::policy::PolicyViolation::ResourceLimit {
1785 resource: crate::policy::resource_name::AGGREGATE_ENCRYPTION_CIPHER_VALUE_BYTES,
1786 maximum,
1787 actual: retained_cipher_bytes.saturating_add(payload.normalized_len),
1788 }
1789 .into());
1790 }
1791 *retained_cipher_bytes += payload.normalized_len;
1792 Ok(CipherData::Value {
1793 value: payload
1794 .normalized()
1795 .map_err(|reason| XmlEncError::Base64(format!("CipherValue: {reason}")))?,
1796 })
1797}
1798
1799fn bounded_simple_text(
1800 node: Node<'_, '_>,
1801 field: &'static str,
1802 policy: ParsingPolicy<'_>,
1803) -> Result<String, XmlEncError> {
1804 bounded_simple_text_with_limit(node, field, policy.resources.max_encryption_metadata_bytes)
1805}
1806
1807pub(super) fn bounded_simple_text_with_limit(
1808 node: Node<'_, '_>,
1809 field: &'static str,
1810 maximum: usize,
1811) -> Result<String, XmlEncError> {
1812 if node.children().any(|child| child.is_element()) {
1813 return Err(XmlEncError::InvalidStructure(format!(
1814 "{field} must not contain element children"
1815 )));
1816 }
1817 let mut value = String::new();
1818 for text in node
1819 .children()
1820 .filter(Node::is_text)
1821 .filter_map(|child| child.text())
1822 {
1823 let actual = value.len().saturating_add(text.len());
1824 validate_metadata_len(actual, maximum)?;
1825 value.push_str(text);
1826 }
1827 Ok(value)
1828}
1829
1830fn bounded_attribute(
1831 node: Node<'_, '_>,
1832 attribute: &str,
1833 policy: ParsingPolicy<'_>,
1834) -> Result<Option<String>, XmlEncError> {
1835 let Some(value) = node.attribute(attribute) else {
1836 return Ok(None);
1837 };
1838 validate_metadata_len(value.len(), policy.resources.max_encryption_metadata_bytes)?;
1839 Ok(Some(value.to_owned()))
1840}
1841
1842pub(super) fn validate_metadata_len(actual: usize, maximum: usize) -> Result<(), XmlEncError> {
1843 if actual <= maximum {
1844 Ok(())
1845 } else {
1846 Err(crate::policy::PolicyViolation::ResourceLimit {
1847 resource: crate::policy::resource_name::ENCRYPTION_METADATA_BYTES,
1848 maximum,
1849 actual,
1850 }
1851 .into())
1852 }
1853}
1854
1855pub(super) fn validate_encrypted_data_metadata<'a>(
1856 encrypted: &EncryptedData,
1857 policy: impl Into<ParsingPolicy<'a>>,
1858) -> Result<(), XmlEncError> {
1859 let policy = policy.into();
1860 validate_encrypted_data_metadata_inner(encrypted, policy, false)
1861}
1862
1863fn validate_encrypted_data_metadata_inner(
1864 encrypted: &EncryptedData,
1865 policy: ParsingPolicy<'_>,
1866 template: bool,
1867) -> Result<(), XmlEncError> {
1868 let maximum = policy.resources.max_encryption_metadata_bytes;
1869 let validate = |value: Option<&str>| validate_metadata_len(value.map_or(0, str::len), maximum);
1870 validate(encrypted.id.as_deref())?;
1871 if let Some(encrypted_type) = encrypted.encrypted_type.as_ref() {
1872 let value = match encrypted_type {
1873 EncryptedDataType::Element => "http://www.w3.org/2001/04/xmlenc#Element",
1874 EncryptedDataType::Content => "http://www.w3.org/2001/04/xmlenc#Content",
1875 EncryptedDataType::Other(value) => value,
1876 };
1877 validate(Some(value))?;
1878 }
1879 validate(encrypted.key_name.as_deref())?;
1880 validate_encryption_method_metadata(&encrypted.encryption_method, maximum)?;
1881 if !encrypted.encapsulation_methods.is_empty() {
1882 policy
1883 .key_establishment
1884 .check_kem_content(super::DataEncryptionAlgorithm::from_uri(
1885 &encrypted.encryption_method.algorithm,
1886 )?)?;
1887 }
1888 validate_key_sources(
1889 &encrypted.encrypted_keys,
1890 &encrypted.derived_keys,
1891 &encrypted.agreement_methods,
1892 &encrypted.encapsulation_methods,
1893 KeySourceValidation { policy, template },
1894 0,
1895 &mut 0,
1896 )
1897}
1898
1899#[derive(Clone, Copy)]
1900struct KeySourceValidation<'a> {
1901 policy: ParsingPolicy<'a>,
1902 template: bool,
1903}
1904
1905fn validate_key_sources(
1906 keys: &[EncryptedKey],
1907 derived: &[super::DerivedKey],
1908 agreements: &[super::AgreementMethod],
1909 encapsulations: &[crate::key_establishment::EncapsulationMechanism],
1910 validation: KeySourceValidation<'_>,
1911 depth: usize,
1912 count: &mut usize,
1913) -> Result<(), XmlEncError> {
1914 let KeySourceValidation { policy, template } = validation;
1915 policy.resources.validate_key_info_reference_depth(depth)?;
1916 let actual = count
1917 .saturating_add(keys.len())
1918 .saturating_add(derived.len())
1919 .saturating_add(agreements.len());
1920 let actual = actual.saturating_add(encapsulations.len());
1921 policy.resources.validate_key_candidates(actual)?;
1922 *count = actual;
1923 let maximum = policy.resources.max_encryption_metadata_bytes;
1924 let validate = |value: Option<&str>| validate_metadata_len(value.map_or(0, str::len), maximum);
1925 for descriptor in encapsulations {
1926 policy
1927 .key_establishment
1928 .check_encapsulation(descriptor.algorithm)?;
1929 if descriptor.ciphertext.len() != descriptor.algorithm.ciphertext_len()
1930 && !(template && descriptor.ciphertext.is_empty())
1931 {
1932 return Err(crate::key_establishment::KeyEstablishmentError::Structure(
1937 "ciphertext size does not match Algorithm",
1938 )
1939 .into());
1940 }
1941 super::agreement::validate_role_metadata(&descriptor.key_info, maximum)?;
1942 *count = count.saturating_add(descriptor.key_info.sources.len());
1943 policy.resources.validate_key_candidates(*count)?;
1944 }
1945 for descriptor in agreements {
1946 validate(Some(descriptor.algorithm.uri()))?;
1947 validate(descriptor.legacy_digest.as_deref())?;
1948 validate_metadata_len(descriptor.nonce.len(), maximum)?;
1949 for role in descriptor.originator.iter().chain(&descriptor.recipient) {
1950 super::agreement::validate_role_metadata(role, maximum)?;
1951 *count = count.saturating_add(role.embedded_candidate_count());
1952 policy.resources.validate_key_candidates(*count)?;
1953 }
1954 if let Some(method) = &descriptor.method {
1955 method.validate_metadata(maximum)?;
1956 }
1957 }
1958 for descriptor in derived {
1959 validate(descriptor.id.as_deref())?;
1960 validate(descriptor.recipient.as_deref())?;
1961 validate(descriptor.key_type.as_deref())?;
1962 validate(descriptor.derived_key_name.as_deref())?;
1963 validate(descriptor.master_key_name.as_deref())?;
1964 if let Some(method) = &descriptor.method {
1965 method.validate_metadata(maximum)?;
1966 }
1967 if let Some(list) = &descriptor.reference_list {
1968 validate_reference_list(list, policy.resources)?;
1969 }
1970 }
1971 for key in keys {
1972 validate(key.id.as_deref())?;
1973 validate(key.recipient.as_deref())?;
1974 validate(key.key_name.as_deref())?;
1975 validate(key.carried_key_name.as_deref())?;
1976 validate_encryption_method_metadata(&key.encryption_method, maximum)?;
1977 if let Some(references) = key.reference_list.as_ref() {
1978 validate_reference_list(references, policy.resources)?;
1979 }
1980 validate_key_sources(
1981 &key.sources.encrypted_keys,
1982 &key.sources.derived_keys,
1983 &key.sources.agreement_methods,
1984 &key.sources.encapsulation_methods,
1985 validation,
1986 depth + 1,
1987 count,
1988 )?;
1989 }
1990 Ok(())
1991}
1992
1993fn validate_reference_list(
1994 references: &ReferenceList,
1995 resources: &crate::policy::ResourcePolicy,
1996) -> Result<(), XmlEncError> {
1997 let count = references
1998 .data_references
1999 .len()
2000 .saturating_add(references.key_references.len());
2001 if count > resources.max_references {
2002 return Err(crate::policy::PolicyViolation::ResourceLimit {
2003 resource: crate::policy::resource_name::ENCRYPTION_REFERENCES,
2004 maximum: resources.max_references,
2005 actual: count,
2006 }
2007 .into());
2008 }
2009 if count == 0 {
2010 return Err(XmlEncError::InvalidStructure(
2011 "ReferenceList must contain at least one reference".into(),
2012 ));
2013 }
2014 for uri in references
2015 .data_references
2016 .iter()
2017 .chain(&references.key_references)
2018 {
2019 validate_metadata_len(uri.len(), resources.max_encryption_metadata_bytes)?;
2020 }
2021 Ok(())
2022}
2023
2024fn validate_encryption_method_metadata(
2025 method: &EncryptionMethod,
2026 maximum: usize,
2027) -> Result<(), XmlEncError> {
2028 validate_metadata_len(method.algorithm.len(), maximum)?;
2029 if let Some(value) = method.oaep_digest.as_deref() {
2030 validate_metadata_len(value.len(), maximum)?;
2031 }
2032 if let Some(value) = method.mgf_algorithm.as_deref() {
2033 validate_metadata_len(value.len(), maximum)?;
2034 }
2035 if let Some(value) = method.oaep_params.as_deref() {
2036 validate_metadata_len(value.len(), maximum)?;
2037 }
2038 if let Some(parameters) = &method.chacha {
2039 if parameters.nonce.is_some() {
2040 validate_metadata_len(12, maximum)?;
2041 }
2042 if parameters.counter.is_some() {
2043 validate_metadata_len(4, maximum)?;
2044 }
2045 if let Some(aad) = parameters.aad.as_deref() {
2046 validate_metadata_len(aad.len(), maximum)?;
2047 }
2048 }
2049 Ok(())
2050}
2051
2052fn validate_encrypted_type_attributes(
2053 node: Node<'_, '_>,
2054 policy: ParsingPolicy<'_>,
2055) -> Result<(), XmlEncError> {
2056 for attribute in ["Id", "Recipient", "Type", "MimeType", "Encoding"] {
2060 if let Some(value) = node.attribute(attribute) {
2061 validate_metadata_len(value.len(), policy.resources.max_encryption_metadata_bytes)?;
2062 }
2063 }
2064 Ok(())
2065}
2066
2067fn parse_encrypted_data_type(value: Option<&str>) -> Option<EncryptedDataType> {
2068 value.map(|value| match value {
2069 "http://www.w3.org/2001/04/xmlenc#Element" => EncryptedDataType::Element,
2070 "http://www.w3.org/2001/04/xmlenc#Content" => EncryptedDataType::Content,
2071 other => EncryptedDataType::Other(other.to_owned()),
2072 })
2073}
2074
2075pub(super) fn decode_bounded_base64_text(
2076 node: Node<'_, '_>,
2077 field: &'static str,
2078 maximum: usize,
2079) -> Result<Vec<u8>, XmlEncError> {
2080 if node.children().any(|child| child.is_element()) {
2081 return Err(XmlEncError::InvalidStructure(format!(
2082 "{field} must not contain element children"
2083 )));
2084 }
2085 let encoded_limit = maximum.div_ceil(3).saturating_mul(4);
2086 let payload =
2090 crate::xmldsig::whitespace::XmlBase64Payload::bounded(node, usize::MAX, encoded_limit)
2091 .map_err(|reason| {
2092 if reason == "maximum allowed base64 length" {
2093 XmlEncError::Policy(crate::policy::PolicyViolation::ResourceLimit {
2094 resource: crate::policy::resource_name::ENCRYPTION_METADATA_BYTES,
2095 maximum,
2096 actual: maximum.saturating_add(1),
2097 })
2098 } else {
2099 XmlEncError::Base64(format!("{field}: {reason}"))
2100 }
2101 })?;
2102 validate_metadata_len(payload.decoded_len, maximum)?;
2103 payload
2104 .decode()
2105 .map_err(|reason| XmlEncError::Base64(format!("{field}: {reason}")))
2106}
2107
2108#[cfg(test)]
2109fn normalize_base64(value: &str) -> Result<String, XmlEncError> {
2110 let xml =
2112 format!("<CipherData xmlns='{XMLENC_NS}'><CipherValue>{value}</CipherValue></CipherData>");
2113 let document = Document::parse(&xml).expect("base64 test text forms XML");
2114 let policy = crate::policy::DecryptionPolicy::default();
2115 let mut budget =
2116 crate::xmldsig::transforms::XPathSignatureParseBudget::from_resources(&policy.resources);
2117 let CipherData::Value { value } = parse_cipher_data(
2118 document.root_element(),
2119 false,
2120 (&policy).into(),
2121 &mut budget,
2122 &mut 0,
2123 )?
2124 else {
2125 unreachable!("inline fixture")
2126 };
2127 Ok(value)
2128}
2129
2130pub(super) fn require_element(
2131 node: Node<'_, '_>,
2132 namespace: &str,
2133 name: &str,
2134) -> Result<(), XmlEncError> {
2135 if node.has_tag_name((namespace, name)) {
2136 Ok(())
2137 } else {
2138 Err(XmlEncError::InvalidStructure(format!(
2139 "expected {{{namespace}}}{name}"
2140 )))
2141 }
2142}
2143
2144fn element_children<'a>(
2145 node: Node<'a, 'a>,
2146) -> std::iter::Peekable<impl Iterator<Item = Node<'a, 'a>>> {
2147 node.children().filter(Node::is_element).peekable()
2148}
2149
2150fn next_required<'a, I>(
2151 children: &mut std::iter::Peekable<I>,
2152 expected: &'static str,
2153) -> Result<Node<'a, 'a>, XmlEncError>
2154where
2155 I: Iterator<Item = Node<'a, 'a>>,
2156{
2157 children
2158 .next()
2159 .ok_or(XmlEncError::MissingRequired(expected))
2160}
2161
2162#[cfg(test)]
2163mod tests {
2164 use super::*;
2165
2166 #[test]
2167 fn chacha_accepts_rfc9231_parameter_elements() {
2168 for (algorithm, tail) in [
2170 ("chacha20", "<Counter>01000000</Counter>"),
2171 ("chacha20poly1305", "<AAD>payload</AAD>"),
2172 ] {
2173 let xml = format!(
2174 "<x:EncryptionMethod xmlns:x='{XMLENC_NS}' Algorithm='http://www.w3.org/2021/04/xmldsig-more#{algorithm}'><Nonce>000102030405060708090a0b</Nonce>{tail}</x:EncryptionMethod>"
2175 );
2176 let document = Document::parse(&xml).expect("RFC parameter XML");
2177 let method = parse_encryption_method_with_limit(document.root_element(), 128)
2178 .expect("RFC parameter grammar must be accepted");
2179 assert_eq!(
2180 method
2181 .chacha
2182 .as_ref()
2183 .expect("parsed ChaCha parameters")
2184 .nonce,
2185 Some([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11])
2186 );
2187 }
2188 }
2189
2190 #[test]
2191 fn chacha_namespace_aliases_do_not_bypass_duplicate_validation() {
2192 for child in [
2194 "<c:Nonce>000102030405060708090a0b</c:Nonce>",
2195 "<foreign:AAD xmlns:foreign='urn:foreign'>payload</foreign:AAD>",
2196 ] {
2197 let xml = format!(
2198 "<x:EncryptionMethod xmlns:x='{XMLENC_NS}' xmlns:c='http://www.w3.org/2021/04/xmldsig-more#' Algorithm='http://www.w3.org/2021/04/xmldsig-more#chacha20poly1305'><Nonce>000102030405060708090a0b</Nonce>{child}</x:EncryptionMethod>"
2199 );
2200 let document = Document::parse(&xml).expect("well-formed namespace regression XML");
2201 assert!(matches!(
2202 parse_encryption_method_with_limit(document.root_element(), 128),
2203 Err(XmlEncError::InvalidStructure(_))
2204 ));
2205 }
2206 }
2207
2208 const DATA: &str = "<xenc:EncryptedData xmlns:xenc=\"http://www.w3.org/2001/04/xmlenc#\" Type=\"http://www.w3.org/2001/04/xmlenc#Element\"><xenc:EncryptionMethod Algorithm=\"http://www.w3.org/2009/xmlenc11#aes128-gcm\"/><xenc:CipherData><xenc:CipherValue> YWJj\nZA== </xenc:CipherValue></xenc:CipherData></xenc:EncryptedData>";
2209
2210 #[test]
2211 fn chacha_parameter_syntax_rejects_malformed_and_cross_profile_values() {
2212 let nonce = "<c:Nonce>000102030405060708090a0b</c:Nonce>";
2216 let counter = "<c:Counter>01000000</c:Counter>";
2217 for (algorithm, children) in [
2218 ("chacha20", format!("{nonce}{counter}{nonce}")),
2219 ("chacha20", format!("{nonce}{counter}{counter}")),
2220 ("chacha20", format!("{nonce}{counter}<c:AAD/>")),
2221 (
2222 "chacha20",
2223 format!("{nonce}<c:Counter>0100000z</c:Counter>"),
2224 ),
2225 ("chacha20", format!("{nonce}<c:Counter>010000</c:Counter>")),
2226 (
2227 "chacha20",
2228 format!("<c:Nonce>000102030405060708090a</c:Nonce>{counter}"),
2229 ),
2230 (
2231 "chacha20",
2232 format!("<c:Nonce><c:Nonce/></c:Nonce>{counter}"),
2233 ),
2234 ("chacha20poly1305", format!("{nonce}{counter}")),
2235 ("chacha20poly1305", format!("{nonce}<c:AAD/><c:AAD/>")),
2236 (
2237 "chacha20poly1305",
2238 format!("{nonce}<c:AAD><c:AAD/></c:AAD>"),
2239 ),
2240 ] {
2241 let xml = format!(
2242 "<x:EncryptionMethod xmlns:x='{XMLENC_NS}' xmlns:c='http://www.w3.org/2021/04/xmldsig-more#' Algorithm='http://www.w3.org/2021/04/xmldsig-more#{algorithm}'>{children}</x:EncryptionMethod>"
2243 );
2244 let document = Document::parse(&xml).expect("well-formed malformed-parameter case");
2245 assert!(
2246 matches!(
2247 parse_encryption_method_with_limit(document.root_element(), 1024),
2248 Err(XmlEncError::InvalidStructure(_))
2249 ),
2250 "{algorithm}: {children}"
2251 );
2252 }
2253 }
2254
2255 #[test]
2256 fn chacha_metadata_budget_and_xml_text_are_preserved() {
2257 let aad = " <payload>\n";
2260 let xml = format!(
2261 "<x:EncryptionMethod xmlns:x='{XMLENC_NS}' xmlns:c='http://www.w3.org/2021/04/xmldsig-more#' Algorithm='http://www.w3.org/2021/04/xmldsig-more#chacha20poly1305'><c:Nonce> 000102030405060708090a0b </c:Nonce><c:AAD>{aad}</c:AAD></x:EncryptionMethod>"
2262 );
2263 let document = Document::parse(&xml).expect("AAD XML");
2264 let parsed =
2265 parse_encryption_method_with_limit(document.root_element(), 128).expect("bounded AAD");
2266 let parameters = parsed.chacha.expect("ChaCha metadata");
2267 assert_eq!(parameters.aad.as_deref(), Some(" <payload>\n"));
2268 assert_eq!(
2269 parameters.nonce,
2270 Some([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11])
2271 );
2272 let oversized = xml.replace(aad, &"a".repeat(129));
2273 let document = Document::parse(&oversized).expect("oversized AAD XML");
2274 assert!(matches!(
2275 parse_encryption_method_with_limit(document.root_element(), 128),
2276 Err(XmlEncError::Policy(
2277 crate::policy::PolicyViolation::ResourceLimit {
2278 maximum: 128,
2279 actual: 129,
2280 ..
2281 }
2282 ))
2283 ));
2284 }
2285
2286 #[test]
2287 fn parses_supported_encrypted_data_and_normalizes_cipher_value() {
2288 let parsed = parse_encrypted_data(DATA).expect("valid XMLEnc data must parse");
2290 assert_eq!(parsed.cipher_data.inline_value(), Some("YWJjZA=="));
2291 assert_eq!(parsed.encrypted_type, Some(EncryptedDataType::Element));
2292 }
2293
2294 #[cfg(feature = "xml-backend-differential")]
2295 #[test]
2296 fn node_revalidation_uses_the_operation_backend() {
2297 let backend = crate::XmlBackend::Roxmltree;
2300 let document = Document::parse_with_backend(DATA, backend).expect("test XML must parse");
2301 let expected_work = DATA.len() * 3;
2302 let resources = crate::policy::ResourcePolicy {
2303 max_xml_parse_work_bytes: expected_work,
2304 ..crate::policy::ResourcePolicy::default()
2305 };
2306 let policy = crate::policy::DecryptionPolicy {
2307 resources: resources.clone(),
2308 ..crate::policy::DecryptionPolicy::default()
2309 };
2310 let budget = XmlParseWorkBudget::from_resources(&resources);
2311
2312 parse_encrypted_data_node_with_context_and_budget(
2313 document.root_element(),
2314 &policy,
2315 &budget,
2316 backend,
2317 crate::provider::default_provider(),
2318 &[],
2319 )
2320 .expect("node revalidation must retain the selected backend");
2321 assert_eq!(budget.consumed(), expected_work);
2322
2323 parse_encrypted_data_node_with_policy_and_backend(
2324 document.root_element(),
2325 &policy,
2326 backend,
2327 )
2328 .expect("public decryption parser must retain the selected backend");
2329 let encryption_policy = crate::policy::EncryptionPolicy {
2330 resources,
2331 ..crate::policy::EncryptionPolicy::default()
2332 };
2333 parse_encrypted_data_template_node_with_policy_and_backend(
2334 document.root_element(),
2335 &encryption_policy,
2336 backend,
2337 )
2338 .expect("public template parser must retain the selected backend");
2339 }
2340
2341 #[test]
2342 fn node_parsers_enforce_the_containing_document_byte_limit() {
2343 let containing = format!("<root>{}<payload/></root>", DATA);
2346 let document = Document::parse(&containing).expect("containing document must parse");
2347 let encrypted_data = document
2348 .descendants()
2349 .find(|node| node.has_tag_name((XMLENC_NS, "EncryptedData")))
2350 .expect("selected EncryptedData");
2351 let resources = crate::policy::ResourcePolicy {
2352 max_xml_document_bytes: DATA.len(),
2353 ..crate::policy::ResourcePolicy::default()
2354 };
2355 let decryption = crate::policy::DecryptionPolicy {
2356 resources: resources.clone(),
2357 ..crate::policy::DecryptionPolicy::default()
2358 };
2359 let encryption = crate::policy::EncryptionPolicy {
2360 resources,
2361 ..crate::policy::EncryptionPolicy::default()
2362 };
2363
2364 for result in [
2365 parse_encrypted_data_node_with_policy(encrypted_data, &decryption),
2366 parse_encrypted_data_template_node_with_policy(encrypted_data, &encryption),
2367 ] {
2368 assert!(matches!(
2369 result,
2370 Err(XmlEncError::Policy(
2371 crate::policy::PolicyViolation::ResourceLimit {
2372 resource: crate::policy::resource_name::XML_DOCUMENT,
2373 ..
2374 }
2375 ))
2376 ));
2377 }
2378 }
2379
2380 #[test]
2381 fn node_parsers_enforce_the_containing_document_node_limit() {
2382 let containing = format!("<root>{}<payload/></root>", DATA);
2385 let document = Document::parse(&containing).expect("containing document must parse");
2386 let encrypted_data = document
2387 .descendants()
2388 .find(|node| node.has_tag_name((XMLENC_NS, "EncryptedData")))
2389 .expect("selected EncryptedData");
2390 let actual_nodes = document.root().descendants().count();
2391 let resources = crate::policy::ResourcePolicy {
2392 max_xml_nodes: actual_nodes - 1,
2393 ..crate::policy::ResourcePolicy::default()
2394 };
2395 let decryption = crate::policy::DecryptionPolicy {
2396 resources: resources.clone(),
2397 ..crate::policy::DecryptionPolicy::default()
2398 };
2399 let encryption = crate::policy::EncryptionPolicy {
2400 resources,
2401 ..crate::policy::EncryptionPolicy::default()
2402 };
2403
2404 for result in [
2405 parse_encrypted_data_node_with_policy(encrypted_data, &decryption),
2406 parse_encrypted_data_template_node_with_policy(encrypted_data, &encryption),
2407 ] {
2408 assert!(matches!(
2409 result,
2410 Err(XmlEncError::Policy(
2411 crate::policy::PolicyViolation::ResourceLimit {
2412 resource: "XML nodes",
2413 maximum,
2414 actual,
2415 }
2416 )) if maximum == actual_nodes - 1 && actual == actual_nodes
2417 ));
2418 }
2419
2420 let exact_resources = crate::policy::ResourcePolicy {
2421 max_xml_nodes: actual_nodes,
2422 ..crate::policy::ResourcePolicy::default()
2423 };
2424 let exact_policy = crate::policy::DecryptionPolicy {
2425 resources: exact_resources,
2426 ..crate::policy::DecryptionPolicy::default()
2427 };
2428 parse_encrypted_data_node_with_policy(encrypted_data, &exact_policy)
2429 .expect("a document exactly at the node ceiling must parse");
2430 }
2431
2432 #[test]
2433 fn policy_parsers_enforce_the_complete_document_depth() {
2434 let containing = format!("<outer><inner>{DATA}</inner></outer>");
2437 let document = Document::parse(&containing).expect("containing document must parse");
2438 let encrypted_data = document
2439 .descendants()
2440 .find(|node| node.has_tag_name((XMLENC_NS, "EncryptedData")))
2441 .expect("selected EncryptedData");
2442 let actual_depth = encrypted_data
2443 .document()
2444 .descendants()
2445 .filter(|node| node.is_element())
2446 .map(|node| {
2447 node.ancestors()
2448 .filter(|ancestor| ancestor.is_element())
2449 .count()
2450 })
2451 .max()
2452 .expect("fixture has elements");
2453 let resources = crate::policy::ResourcePolicy {
2454 max_xml_depth: actual_depth - 1,
2455 ..crate::policy::ResourcePolicy::default()
2456 };
2457 let decryption = crate::policy::DecryptionPolicy {
2458 resources: resources.clone(),
2459 ..crate::policy::DecryptionPolicy::default()
2460 };
2461 let encryption = crate::policy::EncryptionPolicy {
2462 resources,
2463 ..crate::policy::EncryptionPolicy::default()
2464 };
2465
2466 for result in [
2467 parse_encrypted_data_node_with_policy(encrypted_data, &decryption),
2468 parse_encrypted_data_template_node_with_policy(encrypted_data, &encryption),
2469 ] {
2470 assert!(matches!(
2471 result,
2472 Err(XmlEncError::Policy(
2473 crate::policy::PolicyViolation::ResourceLimit {
2474 resource: crate::policy::resource_name::XML_DEPTH,
2475 maximum,
2476 actual,
2477 }
2478 )) if maximum == actual_depth - 1 && actual == actual_depth
2479 ));
2480 }
2481 }
2482
2483 #[test]
2484 fn node_parsers_revalidate_the_containing_documents_dtd_policy() {
2485 let containing = format!(
2488 r#"<!DOCTYPE root [<!ENTITY marker "allowed">]><root>{DATA}<payload>▮</payload></root>"#
2489 );
2490 let document = Document::parse_with_options(
2491 &containing,
2492 ParsingOptions {
2493 allow_dtd: true,
2494 ..ParsingOptions::default()
2495 },
2496 )
2497 .expect("the caller can parse a document under a more permissive policy");
2498 let encrypted_data = document
2499 .descendants()
2500 .find(|node| node.has_tag_name((XMLENC_NS, "EncryptedData")))
2501 .expect("selected EncryptedData");
2502
2503 for result in [
2504 parse_encrypted_data_node_with_policy(
2505 encrypted_data,
2506 &crate::policy::DecryptionPolicy::default(),
2507 ),
2508 parse_encrypted_data_template_node_with_policy(
2509 encrypted_data,
2510 &crate::policy::EncryptionPolicy::default(),
2511 ),
2512 ] {
2513 assert!(matches!(result, Err(XmlEncError::XmlParse(_))));
2514 }
2515
2516 let mut decryption_allowed = crate::policy::DecryptionPolicy::default();
2517 decryption_allowed.xml.allow_internal_dtd = true;
2518 parse_encrypted_data_node_with_policy(encrypted_data, &decryption_allowed)
2519 .expect("explicitly permitted internal DTD must remain accepted");
2520 let mut encryption_allowed = crate::policy::EncryptionPolicy::default();
2521 encryption_allowed.xml.allow_internal_dtd = true;
2522 parse_encrypted_data_template_node_with_policy(encrypted_data, &encryption_allowed)
2523 .expect("template parsing must share the same explicit DTD policy");
2524 }
2525
2526 #[test]
2527 fn template_parser_rejects_nonempty_invalid_cipher_values() {
2528 let invalid_direct = DATA.replace(" YWJj\nZA== ", "!!!!");
2531 let invalid_recipient = format!(
2532 "<xenc:EncryptedData xmlns:xenc=\"{XMLENC_NS}\" xmlns:ds=\"{XMLDSIG_NS}\"><xenc:EncryptionMethod Algorithm=\"http://www.w3.org/2009/xmlenc11#aes128-gcm\"/><ds:KeyInfo><xenc:EncryptedKey><xenc:EncryptionMethod Algorithm=\"http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p\"/><xenc:CipherData><xenc:CipherValue>!!!!</xenc:CipherValue></xenc:CipherData></xenc:EncryptedKey></ds:KeyInfo><xenc:CipherData><xenc:CipherValue/></xenc:CipherData></xenc:EncryptedData>"
2533 );
2534 for xml in [&invalid_direct, &invalid_recipient] {
2535 let document = Document::parse(xml).expect("template must be well-formed XML");
2536 assert!(matches!(
2537 parse_encrypted_data_template_node_with_policy(
2538 document.root_element(),
2539 &crate::policy::EncryptionPolicy::default(),
2540 ),
2541 Err(XmlEncError::Base64(_))
2542 ));
2543 }
2544
2545 let empty = DATA.replace(" YWJj\nZA== ", "");
2546 let document = Document::parse(&empty).expect("empty template must be XML");
2547 parse_encrypted_data_template_node_with_policy(
2548 document.root_element(),
2549 &crate::policy::EncryptionPolicy::default(),
2550 )
2551 .expect("an explicit empty template placeholder remains valid");
2552 }
2553
2554 #[test]
2555 fn parses_cipher_reference_without_performing_retrieval() {
2556 let xml = "<xenc:EncryptedData xmlns:xenc=\"http://www.w3.org/2001/04/xmlenc#\"><xenc:EncryptionMethod Algorithm=\"http://www.w3.org/2009/xmlenc11#aes128-gcm\"/><xenc:CipherData><xenc:CipherReference URI=\"https://attacker.invalid/key\"/></xenc:CipherData></xenc:EncryptedData>";
2558 assert!(
2559 matches!(parse_encrypted_data(xml).expect("reference descriptor").cipher_data,
2560 CipherData::Reference { ref uri, .. } if uri == "https://attacker.invalid/key")
2561 );
2562 }
2563
2564 #[test]
2565 fn joins_comment_split_cipher_text_and_rejects_element_children() {
2566 let split = DATA.replace("YWJj\nZA==", "YW<!-- split -->Jj\nZA==");
2569 let parsed = parse_encrypted_data(&split).expect("comment-split base64 must parse");
2570 assert_eq!(parsed.cipher_data.inline_value(), Some("YWJjZA=="));
2571
2572 let nested = DATA.replace("YWJj\nZA==", "YW<xenc:Unexpected/>JjZA==");
2573 assert!(matches!(
2574 parse_encrypted_data(&nested),
2575 Err(XmlEncError::InvalidStructure(_))
2576 ));
2577 }
2578
2579 #[test]
2580 fn rejects_wrong_namespaces_and_retains_recipient_keys() {
2581 let wrong_namespace = DATA.replace(XMLENC_NS, "urn:not-xmlenc");
2584 assert!(matches!(
2585 parse_encrypted_data(&wrong_namespace),
2586 Err(XmlEncError::InvalidStructure(_))
2587 ));
2588
2589 let encrypted_key = |recipient: &str| {
2590 format!(
2591 "<xenc:EncryptedKey Recipient=\"{recipient}\"><xenc:EncryptionMethod Algorithm=\"http://www.w3.org/2001/04/xmlenc#kw-aes128\"/><xenc:CipherData><xenc:CipherValue>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA</xenc:CipherValue></xenc:CipherData></xenc:EncryptedKey>"
2592 )
2593 };
2594 let recipients = format!(
2595 "<xenc:EncryptedData xmlns:xenc=\"{XMLENC_NS}\" xmlns:ds=\"{XMLDSIG_NS}\"><xenc:EncryptionMethod Algorithm=\"http://www.w3.org/2009/xmlenc11#aes128-gcm\"/><ds:KeyInfo>{}{}</ds:KeyInfo><xenc:CipherData><xenc:CipherValue>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==</xenc:CipherValue></xenc:CipherData></xenc:EncryptedData>",
2596 encrypted_key("alice"),
2597 encrypted_key("bob")
2598 );
2599 let parsed = parse_encrypted_data(&recipients).expect("recipient keys must parse");
2600 assert_eq!(
2601 parsed
2602 .encrypted_keys
2603 .iter()
2604 .filter_map(|key| key.recipient.as_deref())
2605 .collect::<Vec<_>>(),
2606 ["alice", "bob"]
2607 );
2608 }
2609
2610 #[test]
2613 fn rejects_unsupported_key_agreement_explicitly() {
2614 let xml = format!(
2615 r#"<xenc:EncryptedData xmlns:xenc="{XMLENC_NS}" xmlns:ds="{XMLDSIG_NS}"><xenc:EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#aes128-cbc"/><ds:KeyInfo><xenc:AgreementMethod Algorithm="http://www.w3.org/2001/04/xmlenc#dh"/></ds:KeyInfo><xenc:CipherData><xenc:CipherValue>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=</xenc:CipherValue></xenc:CipherData></xenc:EncryptedData>"#
2616 );
2617 let parsed = parse_encrypted_data(&xml).expect("legacy DH descriptor parses");
2618 assert_eq!(
2619 parsed.agreement_methods[0].algorithm,
2620 crate::policy::KeyAgreementAlgorithm::LegacyDh
2621 );
2622 let unknown = xml.replace(
2623 "http://www.w3.org/2001/04/xmlenc#dh",
2624 "urn:unknown:agreement",
2625 );
2626 assert!(matches!(
2627 parse_encrypted_data(&unknown),
2628 Err(XmlEncError::UnsupportedAlgorithm(uri))
2629 if uri == "urn:unknown:agreement"
2630 ));
2631
2632 let missing_algorithm =
2633 xml.replace(" Algorithm=\"http://www.w3.org/2001/04/xmlenc#dh\"", "");
2634 assert!(matches!(
2635 parse_encrypted_data(&missing_algorithm),
2636 Err(XmlEncError::MissingRequired("AgreementMethod Algorithm"))
2637 ));
2638 }
2639
2640 #[test]
2642 fn retains_supported_key_candidates_alongside_unsupported_agreement() {
2643 let xml = format!(
2645 r#"<xenc:EncryptedData xmlns:xenc="{XMLENC_NS}" xmlns:ds="{XMLDSIG_NS}"><xenc:EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#aes128-cbc"/><ds:KeyInfo><xenc:AgreementMethod Algorithm="http://www.w3.org/2001/04/xmlenc#dh"/><ds:KeyName>content-key</ds:KeyName><xenc:EncryptedKey Recipient="alice"><xenc:EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#kw-aes128"/><xenc:CipherData><xenc:CipherValue>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA</xenc:CipherValue></xenc:CipherData></xenc:EncryptedKey></ds:KeyInfo><xenc:CipherData><xenc:CipherValue>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=</xenc:CipherValue></xenc:CipherData></xenc:EncryptedData>"#
2646 );
2647
2648 let parsed = parse_encrypted_data(&xml)
2649 .expect("a supported key candidate must take precedence over agreement fallback");
2650 assert_eq!(parsed.key_name.as_deref(), Some("content-key"));
2651 assert_eq!(parsed.encrypted_keys.len(), 1);
2652 assert_eq!(parsed.agreement_methods.len(), 1);
2653 assert_eq!(parsed.encrypted_keys[0].recipient.as_deref(), Some("alice"));
2654 }
2655
2656 #[test]
2657 fn rejects_missing_algorithm_and_duplicate_oaep_parameters() {
2658 let missing_algorithm = DATA.replace(
2661 " Algorithm=\"http://www.w3.org/2009/xmlenc11#aes128-gcm\"",
2662 "",
2663 );
2664 assert!(matches!(
2665 parse_encrypted_data(&missing_algorithm),
2666 Err(XmlEncError::MissingRequired(_))
2667 ));
2668
2669 let duplicate_oaep = format!(
2670 "<xenc:EncryptedData xmlns:xenc=\"{XMLENC_NS}\"><xenc:EncryptionMethod Algorithm=\"http://www.w3.org/2009/xmlenc11#aes128-gcm\"><xenc:OAEPparams>YQ==</xenc:OAEPparams><xenc:OAEPparams>Yg==</xenc:OAEPparams></xenc:EncryptionMethod><xenc:CipherData><xenc:CipherValue>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==</xenc:CipherValue></xenc:CipherData></xenc:EncryptedData>"
2671 );
2672 assert!(matches!(
2673 parse_encrypted_data(&duplicate_oaep),
2674 Err(XmlEncError::InvalidStructure(_))
2675 ));
2676
2677 let oaep_on_aes = DATA.replace(
2678 "/><xenc:CipherData>",
2679 "><xenc:OAEPparams>YQ==</xenc:OAEPparams></xenc:EncryptionMethod><xenc:CipherData>",
2680 );
2681 assert!(matches!(
2682 parse_encrypted_data(&oaep_on_aes),
2683 Err(XmlEncError::InvalidStructure(_))
2684 ));
2685 }
2686
2687 #[test]
2688 fn accepts_empty_oaep_params_as_an_explicit_empty_label() {
2689 for params in ["", " \n\t "] {
2692 let xml = format!(
2693 "<xenc:EncryptionMethod xmlns:xenc=\"{XMLENC_NS}\" Algorithm=\"http://www.w3.org/2009/xmlenc11#rsa-oaep\"><xenc:OAEPparams>{params}</xenc:OAEPparams></xenc:EncryptionMethod>"
2694 );
2695 let document = Document::parse(&xml).expect("test method must be XML");
2696 let parsed = parse_encryption_method(document.root_element())
2697 .expect("empty OAEPparams must decode as an empty label");
2698 assert_eq!(parsed.oaep_params, Some(Vec::new()));
2699 }
2700
2701 assert!(matches!(
2702 normalize_base64(" \n\t "),
2703 Err(XmlEncError::Base64(_))
2704 ));
2705 }
2706
2707 #[test]
2708 fn bounds_oaep_parameters_before_base64_allocation() {
2709 let xml = format!(
2712 "<xenc:EncryptionMethod xmlns:xenc=\"{XMLENC_NS}\" Algorithm=\"http://www.w3.org/2009/xmlenc11#rsa-oaep\"><xenc:OAEPparams>{}</xenc:OAEPparams></xenc:EncryptionMethod>",
2713 STANDARD.encode([0_u8; 65])
2714 );
2715 let document = Document::parse(&xml).expect("test method must be XML");
2716
2717 assert!(matches!(
2718 parse_encryption_method_with_limit(document.root_element(), 64),
2719 Err(XmlEncError::Policy(
2720 crate::policy::PolicyViolation::ResourceLimit {
2721 resource: crate::policy::resource_name::ENCRYPTION_METADATA_BYTES,
2722 maximum: 64,
2723 actual: 65,
2724 }
2725 ))
2726 ));
2727 }
2728
2729 #[test]
2730 fn validates_explicit_key_size_for_supported_aes_methods() {
2731 for (algorithm, bits) in [
2734 ("http://www.w3.org/2001/04/xmlenc#aes128-cbc", 128),
2735 ("http://www.w3.org/2001/04/xmlenc#aes256-cbc", 256),
2736 ("http://www.w3.org/2009/xmlenc11#aes128-gcm", 128),
2737 ("http://www.w3.org/2009/xmlenc11#aes256-gcm", 256),
2738 ("http://www.w3.org/2001/04/xmlenc#kw-aes128", 128),
2739 ("http://www.w3.org/2001/04/xmlenc#kw-aes256", 256),
2740 ] {
2741 let xml = format!(
2742 "<xenc:EncryptionMethod xmlns:xenc=\"{XMLENC_NS}\" Algorithm=\"{algorithm}\"><xenc:KeySize>{bits}</xenc:KeySize></xenc:EncryptionMethod>"
2743 );
2744 let document = Document::parse(&xml).expect("test method must be XML");
2745 let parsed = parse_encryption_method(document.root_element())
2746 .expect("matching AES KeySize must parse");
2747 assert_eq!(parsed.key_size_bits, Some(bits));
2748
2749 let inconsistent = xml.replace(&format!(">{bits}<"), ">192<");
2750 let document = Document::parse(&inconsistent).expect("test method must be XML");
2751 assert!(matches!(
2752 parse_encryption_method(document.root_element()),
2753 Err(XmlEncError::InvalidStructure(_))
2754 ));
2755 }
2756
2757 for key_size in ["128.0", "", "128</xenc:KeySize><xenc:KeySize>128"] {
2758 let xml = format!(
2759 "<xenc:EncryptionMethod xmlns:xenc=\"{XMLENC_NS}\" Algorithm=\"http://www.w3.org/2009/xmlenc11#aes128-gcm\"><xenc:KeySize>{key_size}</xenc:KeySize></xenc:EncryptionMethod>"
2760 );
2761 let document = Document::parse(&xml).expect("test method must be XML");
2762 assert!(matches!(
2763 parse_encryption_method(document.root_element()),
2764 Err(XmlEncError::InvalidStructure(_))
2765 ));
2766 }
2767 }
2768
2769 #[test]
2770 fn key_size_text_is_bounded_before_integer_parsing() {
2771 let key_size = format!("{}128", "0".repeat(65));
2774 let xml = format!(
2775 "<xenc:EncryptionMethod xmlns:xenc=\"{XMLENC_NS}\" Algorithm=\"http://www.w3.org/2009/xmlenc11#aes128-gcm\"><xenc:KeySize>{key_size}</xenc:KeySize></xenc:EncryptionMethod>"
2776 );
2777 let document = Document::parse(&xml).expect("test method must be XML");
2778
2779 assert!(matches!(
2780 parse_encryption_method_with_limit(document.root_element(), 64),
2781 Err(XmlEncError::Policy(
2782 crate::policy::PolicyViolation::ResourceLimit {
2783 resource: crate::policy::resource_name::ENCRYPTION_METADATA_BYTES,
2784 maximum: 64,
2785 actual: 68,
2786 }
2787 ))
2788 ));
2789 }
2790
2791 #[test]
2792 fn retains_key_names_and_encrypted_key_reference_list() {
2793 let xml = format!(
2796 r##"<xenc:EncryptedData xmlns:xenc="{XMLENC_NS}" xmlns:ds="{XMLDSIG_NS}" Id="data-1"><xenc:EncryptionMethod Algorithm="http://www.w3.org/2009/xmlenc11#aes128-gcm"/><ds:KeyInfo><ds:KeyName>content-key</ds:KeyName><xenc:EncryptedKey Id="key-1" Recipient="alice"><xenc:EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#kw-aes128"/><ds:KeyInfo><ds:X509Data/><ds:KeyName>wrapping-key</ds:KeyName></ds:KeyInfo><xenc:CipherData><xenc:CipherValue>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA</xenc:CipherValue></xenc:CipherData><xenc:ReferenceList><xenc:DataReference URI="#data-1"/><xenc:KeyReference URI="#key-2"/></xenc:ReferenceList></xenc:EncryptedKey></ds:KeyInfo><xenc:CipherData><xenc:CipherValue>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==</xenc:CipherValue></xenc:CipherData></xenc:EncryptedData>"##
2797 );
2798 let parsed = parse_encrypted_data(&xml).expect("complete key metadata must parse");
2799 assert_eq!(parsed.key_name.as_deref(), Some("content-key"));
2800 let encrypted_key = parsed
2801 .encrypted_keys
2802 .first()
2803 .expect("embedded key must be retained");
2804 assert_eq!(encrypted_key.key_name.as_deref(), Some("wrapping-key"));
2805 let references = encrypted_key
2806 .reference_list
2807 .as_ref()
2808 .expect("reference list must be retained");
2809 assert_eq!(references.data_references, ["#data-1"]);
2810 assert_eq!(references.key_references, ["#key-2"]);
2811 }
2812
2813 #[test]
2814 fn preserves_key_identifier_whitespace() {
2815 let xml = format!(
2818 r#"<xenc:EncryptedData xmlns:xenc="{XMLENC_NS}" xmlns:ds="{XMLDSIG_NS}"><xenc:EncryptionMethod Algorithm="http://www.w3.org/2009/xmlenc11#aes128-gcm"/><ds:KeyInfo><ds:KeyName> content-key </ds:KeyName><xenc:EncryptedKey><xenc:EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#kw-aes128"/><ds:KeyInfo><ds:KeyName> wrapping-key </ds:KeyName></ds:KeyInfo><xenc:CipherData><xenc:CipherValue>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA</xenc:CipherValue></xenc:CipherData><xenc:CarriedKeyName> transported-key </xenc:CarriedKeyName></xenc:EncryptedKey></ds:KeyInfo><xenc:CipherData><xenc:CipherValue>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==</xenc:CipherValue></xenc:CipherData></xenc:EncryptedData>"#
2819 );
2820 let parsed = parse_encrypted_data(&xml).expect("key metadata must parse");
2821 assert_eq!(parsed.key_name.as_deref(), Some(" content-key "));
2822 let encrypted_key = parsed
2823 .encrypted_keys
2824 .first()
2825 .expect("embedded key must be retained");
2826 assert_eq!(encrypted_key.key_name.as_deref(), Some(" wrapping-key "));
2827 assert_eq!(
2828 encrypted_key.carried_key_name.as_deref(),
2829 Some(" transported-key ")
2830 );
2831 }
2832
2833 #[test]
2834 fn accepts_one_carried_key_name_and_rejects_duplicates() {
2835 let xml = format!(
2838 r##"<xenc:EncryptedData xmlns:xenc="{XMLENC_NS}" xmlns:ds="{XMLDSIG_NS}"><xenc:EncryptionMethod Algorithm="http://www.w3.org/2009/xmlenc11#aes128-gcm"/><ds:KeyInfo><xenc:EncryptedKey><xenc:EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#kw-aes128"/><xenc:CipherData><xenc:CipherValue>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA</xenc:CipherValue></xenc:CipherData><xenc:ReferenceList><xenc:DataReference URI="#data-1"/></xenc:ReferenceList><xenc:CarriedKeyName>transported-key</xenc:CarriedKeyName></xenc:EncryptedKey></ds:KeyInfo><xenc:CipherData><xenc:CipherValue>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==</xenc:CipherValue></xenc:CipherData></xenc:EncryptedData>"##
2839 );
2840 let parsed = parse_encrypted_data(&xml).expect("one CarriedKeyName must parse");
2841 assert_eq!(
2842 parsed
2843 .encrypted_keys
2844 .first()
2845 .expect("embedded key must be retained")
2846 .carried_key_name
2847 .as_deref(),
2848 Some("transported-key")
2849 );
2850
2851 let duplicate = xml.replace(
2852 "</xenc:EncryptedKey>",
2853 "<xenc:CarriedKeyName>duplicate</xenc:CarriedKeyName></xenc:EncryptedKey>",
2854 );
2855 assert!(matches!(
2856 parse_encrypted_data(&duplicate),
2857 Err(XmlEncError::InvalidStructure(_))
2858 ));
2859 }
2860
2861 #[test]
2862 fn accepts_encrypted_key_key_info_without_key_name() {
2863 let xml = format!(
2866 r#"<xenc:EncryptedData xmlns:xenc="{XMLENC_NS}" xmlns:ds="{XMLDSIG_NS}"><xenc:EncryptionMethod Algorithm="http://www.w3.org/2009/xmlenc11#aes128-gcm"/><ds:KeyInfo><xenc:EncryptedKey><xenc:EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p"/><ds:KeyInfo><ds:X509Data><ds:X509Certificate>YQ==</ds:X509Certificate></ds:X509Data></ds:KeyInfo><xenc:CipherData><xenc:CipherValue>YQ==</xenc:CipherValue></xenc:CipherData></xenc:EncryptedKey></ds:KeyInfo><xenc:CipherData><xenc:CipherValue>YQ==</xenc:CipherValue></xenc:CipherData></xenc:EncryptedData>"#
2867 );
2868 let parsed = parse_encrypted_data(&xml).expect("certificate-only KeyInfo must parse");
2869 assert_eq!(
2870 parsed
2871 .encrypted_keys
2872 .first()
2873 .expect("embedded key must be retained")
2874 .key_name
2875 .as_deref(),
2876 None
2877 );
2878 }
2879
2880 #[test]
2881 fn rejects_malformed_encrypted_key_reference_lists() {
2882 let template = format!(
2885 r#"<xenc:EncryptedData xmlns:xenc="{XMLENC_NS}" xmlns:ds="{XMLDSIG_NS}"><xenc:EncryptionMethod Algorithm="http://www.w3.org/2009/xmlenc11#aes128-gcm"/><ds:KeyInfo><xenc:EncryptedKey><xenc:EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#kw-aes128"/><xenc:CipherData><xenc:CipherValue>YQ==</xenc:CipherValue></xenc:CipherData>{{reference_list}}</xenc:EncryptedKey></ds:KeyInfo><xenc:CipherData><xenc:CipherValue>YQ==</xenc:CipherValue></xenc:CipherData></xenc:EncryptedData>"#
2886 );
2887 for malformed in [
2888 "<xenc:ReferenceList/>",
2889 "<xenc:ReferenceList><xenc:DataReference/></xenc:ReferenceList>",
2890 "<xenc:ReferenceList><xenc:Unexpected URI=\"#data\"/></xenc:ReferenceList>",
2891 ] {
2892 let xml = template.replace("{reference_list}", malformed);
2893 assert!(
2894 parse_encrypted_data(&xml).is_err(),
2895 "malformed list must fail: {malformed}"
2896 );
2897 }
2898 }
2899
2900 #[test]
2901 fn bounds_normalized_cipher_value_before_decode() {
2902 let oversized = "A".repeat(MAX_CIPHER_VALUE_BASE64_LEN + 1);
2905 let xml = DATA.replace("YWJj\nZA==", &oversized);
2906 assert!(matches!(
2907 parse_encrypted_data(&xml),
2908 Err(XmlEncError::Policy(
2909 crate::policy::PolicyViolation::ResourceLimit {
2910 resource: crate::policy::resource_name::XML_DOCUMENT,
2911 ..
2912 }
2913 ))
2914 ));
2915 }
2916
2917 #[test]
2918 fn policy_bounds_copied_encryption_metadata() {
2919 let policy = crate::policy::DecryptionPolicy {
2922 resources: crate::policy::ResourcePolicy {
2923 max_encryption_metadata_bytes: 64,
2924 ..crate::policy::ResourcePolicy::default()
2925 },
2926 ..crate::policy::DecryptionPolicy::default()
2927 };
2928 let oversized = "x".repeat(65);
2929 for xml in [
2930 DATA.replace("<xenc:EncryptedData ", &format!("<xenc:EncryptedData Id=\"{oversized}\" ")),
2931 DATA.replace(
2932 "<xenc:EncryptedData ",
2933 &format!("<xenc:EncryptedData MimeType=\"{oversized}\" "),
2934 ),
2935 DATA.replace(
2936 "<xenc:EncryptedData ",
2937 &format!("<xenc:EncryptedData Encoding=\"{oversized}\" "),
2938 ),
2939 DATA.replace(
2940 "<xenc:CipherData>",
2941 &format!("<ds:KeyInfo xmlns:ds=\"{XMLDSIG_NS}\"><ds:KeyName>{oversized}</ds:KeyName></ds:KeyInfo><xenc:CipherData>"),
2942 ),
2943 ] {
2944 assert!(matches!(
2945 parse_encrypted_data_with_policy(&xml, &policy),
2946 Err(XmlEncError::Policy(crate::policy::PolicyViolation::ResourceLimit {
2947 maximum: 64,
2948 actual: 65,
2949 ..
2950 }))
2951 ));
2952 }
2953 }
2954
2955 #[test]
2956 fn policy_bounds_common_encrypted_type_metadata_on_nested_keys() {
2957 let resources = crate::policy::ResourcePolicy {
2961 max_encryption_metadata_bytes: 64,
2962 ..crate::policy::ResourcePolicy::default()
2963 };
2964 let decryption = crate::policy::DecryptionPolicy {
2965 resources: resources.clone(),
2966 ..crate::policy::DecryptionPolicy::default()
2967 };
2968 let encryption = crate::policy::EncryptionPolicy {
2969 resources,
2970 ..crate::policy::EncryptionPolicy::default()
2971 };
2972 let oversized = "x".repeat(65);
2973
2974 for attribute in ["Type", "MimeType", "Encoding"] {
2975 let xml = format!(
2976 r#"<xenc:EncryptedData xmlns:xenc="{XMLENC_NS}" xmlns:ds="{XMLDSIG_NS}"><xenc:EncryptionMethod Algorithm="http://www.w3.org/2009/xmlenc11#aes128-gcm"/><ds:KeyInfo><xenc:EncryptedKey {attribute}="{oversized}"><xenc:EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#kw-aes128"/><xenc:CipherData><xenc:CipherValue>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA</xenc:CipherValue></xenc:CipherData></xenc:EncryptedKey></ds:KeyInfo><xenc:CipherData><xenc:CipherValue>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==</xenc:CipherValue></xenc:CipherData></xenc:EncryptedData>"#
2977 );
2978 assert!(matches!(
2979 parse_encrypted_data_with_policy(&xml, &decryption),
2980 Err(XmlEncError::Policy(
2981 crate::policy::PolicyViolation::ResourceLimit {
2982 resource: "encryption metadata bytes",
2983 maximum: 64,
2984 actual: 65,
2985 }
2986 ))
2987 ));
2988
2989 let document = Document::parse(&xml).expect("test template must be XML");
2990 assert!(matches!(
2991 parse_encrypted_data_template_node_with_policy(
2992 document.root_element(),
2993 &encryption,
2994 ),
2995 Err(XmlEncError::Policy(
2996 crate::policy::PolicyViolation::ResourceLimit {
2997 resource: "encryption metadata bytes",
2998 maximum: 64,
2999 actual: 65,
3000 }
3001 ))
3002 ));
3003 }
3004 }
3005
3006 #[test]
3007 fn rejects_non_ascii_base64_before_it_can_cross_the_byte_bound() {
3008 assert!(matches!(
3011 normalize_base64("YWJjéA=="),
3012 Err(XmlEncError::Base64(_))
3013 ));
3014
3015 assert!(matches!(
3018 normalize_base64("AAAAé"),
3019 Err(XmlEncError::Base64(_))
3020 ));
3021 }
3022}