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