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