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