1#[cfg(test)]
4use crate::xml::dom::ParsingOptions;
5use crate::xml::dom::{Document, Node};
6use base64::{Engine as _, engine::general_purpose::STANDARD};
7
8use crate::document::{
9 DocumentParseSettings, XmlParseWorkBudget, parse_borrowed_with_settings_and_budget,
10};
11
12use super::map_document_error;
13use super::types::{
14 CipherData, EncryptedData, EncryptedDataType, EncryptedKey, EncryptionMethod,
15 MAX_CIPHER_VALUE_BASE64_LEN, ReferenceList, XMLDSIG_NS, XMLENC_NS, XMLENC11_NS, XmlEncError,
16};
17
18#[derive(Clone, Copy)]
19pub(super) struct ParsingPolicy<'a> {
20 xml: &'a crate::policy::XmlInputPolicy,
21 resources: &'a crate::policy::ResourcePolicy,
22}
23
24impl<'a> From<&'a crate::policy::EncryptionPolicy> for ParsingPolicy<'a> {
25 fn from(policy: &'a crate::policy::EncryptionPolicy) -> Self {
26 Self {
27 xml: &policy.xml,
28 resources: &policy.resources,
29 }
30 }
31}
32
33impl<'a> From<&'a crate::policy::DecryptionPolicy> for ParsingPolicy<'a> {
34 fn from(policy: &'a crate::policy::DecryptionPolicy) -> Self {
35 Self {
36 xml: &policy.xml,
37 resources: &policy.resources,
38 }
39 }
40}
41
42struct ParsedKeyInfo {
43 key_name: Option<String>,
44 encrypted_keys: Vec<EncryptedKey>,
45}
46
47pub fn parse_encrypted_data(xml: &str) -> Result<EncryptedData, XmlEncError> {
49 parse_encrypted_data_with_policy(xml, &crate::policy::DecryptionPolicy::default())
50}
51
52pub(super) fn parse_encrypted_data_with_policy(
53 xml: &str,
54 policy: &crate::policy::DecryptionPolicy,
55) -> Result<EncryptedData, XmlEncError> {
56 parse_encrypted_data_with_policy_and_backend(xml, policy, crate::XmlBackend::default())
57}
58
59pub(super) fn parse_encrypted_data_with_policy_and_backend(
60 xml: &str,
61 policy: &crate::policy::DecryptionPolicy,
62 backend: crate::XmlBackend,
63) -> Result<EncryptedData, XmlEncError> {
64 policy.validate()?;
65 policy.resources.validate_xml_document_len(xml.len())?;
66 let parse_budget = XmlParseWorkBudget::from_resources(&policy.resources);
67 let settings =
68 DocumentParseSettings::from_policy(&policy.xml, &policy.resources).with_backend(backend);
69 let document = parse_borrowed_with_settings_and_budget(xml, settings, Some(&parse_budget))
70 .map_err(|error| map_document_error(error, settings))?;
71 parse_encrypted_data_node(document.root_element(), policy.into(), false)
72}
73
74pub fn parse_encrypted_data_node_with_policy(
82 node: Node<'_, '_>,
83 policy: &crate::policy::DecryptionPolicy,
84) -> Result<EncryptedData, XmlEncError> {
85 parse_encrypted_data_node_with_policy_and_backend(node, policy, crate::XmlBackend::default())
86}
87
88pub fn parse_encrypted_data_node_with_policy_and_backend(
90 node: Node<'_, '_>,
91 policy: &crate::policy::DecryptionPolicy,
92 backend: crate::XmlBackend,
93) -> Result<EncryptedData, XmlEncError> {
94 let parse_budget = XmlParseWorkBudget::from_resources(&policy.resources);
95 parse_encrypted_data_node_with_policy_and_budget(node, policy, &parse_budget, backend)
96}
97
98pub(super) fn parse_encrypted_data_node_with_policy_and_budget(
99 node: Node<'_, '_>,
100 policy: &crate::policy::DecryptionPolicy,
101 parse_budget: &XmlParseWorkBudget,
102 backend: crate::XmlBackend,
103) -> Result<EncryptedData, XmlEncError> {
104 policy.validate()?;
105 let policy = ParsingPolicy::from(policy);
106 validate_node_document_policy(node, policy, parse_budget, backend)?;
107 parse_encrypted_data_node(node, policy, false)
108}
109
110pub fn parse_encrypted_data_template_node_with_policy(
117 node: Node<'_, '_>,
118 policy: &crate::policy::EncryptionPolicy,
119) -> Result<EncryptedData, XmlEncError> {
120 parse_encrypted_data_template_node_with_policy_and_backend(
121 node,
122 policy,
123 crate::XmlBackend::default(),
124 )
125}
126
127pub fn parse_encrypted_data_template_node_with_policy_and_backend(
129 node: Node<'_, '_>,
130 policy: &crate::policy::EncryptionPolicy,
131 backend: crate::XmlBackend,
132) -> Result<EncryptedData, XmlEncError> {
133 let parse_budget = XmlParseWorkBudget::from_resources(&policy.resources);
134 policy.validate()?;
135 let policy = ParsingPolicy::from(policy);
136 validate_node_document_policy(node, policy, &parse_budget, backend)?;
137 parse_encrypted_data_node(node, policy, true)
138}
139
140fn validate_node_document_policy(
141 node: Node<'_, '_>,
142 policy: ParsingPolicy<'_>,
143 parse_budget: &XmlParseWorkBudget,
144 backend: crate::XmlBackend,
145) -> Result<(), XmlEncError> {
146 parse_policy_document(node.document().input_text(), policy, parse_budget, backend)?;
147 Ok(())
148}
149
150fn parse_policy_document<'a>(
151 xml: &'a str,
152 policy: ParsingPolicy<'_>,
153 parse_budget: &XmlParseWorkBudget,
154 backend: crate::XmlBackend,
155) -> Result<Document<'a>, XmlEncError> {
156 let settings =
157 DocumentParseSettings::from_policy(policy.xml, policy.resources).with_backend(backend);
158 parse_borrowed_with_settings_and_budget(xml, settings, Some(parse_budget))
159 .map_err(|error| map_document_error(error, settings))
160}
161
162fn parse_encrypted_data_node(
163 node: Node<'_, '_>,
164 policy: ParsingPolicy<'_>,
165 allow_empty_cipher_values: bool,
166) -> Result<EncryptedData, XmlEncError> {
167 require_element(node, XMLENC_NS, "EncryptedData")?;
168 validate_encrypted_type_attributes(node, policy)?;
169 let mut children = element_children(node);
170 let encryption_method = parse_encryption_method_with_limit(
171 next_required(&mut children, "EncryptionMethod")?,
172 policy.resources.max_encryption_metadata_bytes,
173 )?;
174 if children
175 .peek()
176 .is_some_and(|child| child.has_tag_name((XMLENC_NS, "EncryptionMethod")))
177 {
178 return Err(XmlEncError::InvalidStructure(
179 "EncryptedData contains more than one direct EncryptionMethod".into(),
180 ));
181 }
182
183 let key_info = match children.peek() {
184 Some(child) if child.has_tag_name((XMLDSIG_NS, "KeyInfo")) => parse_key_info(
185 next_required(&mut children, "KeyInfo")?,
186 policy,
187 allow_empty_cipher_values,
188 )?,
189 _ => ParsedKeyInfo {
190 key_name: None,
191 encrypted_keys: Vec::new(),
192 },
193 };
194 if children
195 .peek()
196 .is_some_and(|child| child.has_tag_name((XMLDSIG_NS, "KeyInfo")))
197 {
198 return Err(XmlEncError::InvalidStructure(
199 "EncryptedData contains more than one direct KeyInfo".into(),
200 ));
201 }
202
203 let cipher_data = parse_cipher_data(
204 next_required(&mut children, "CipherData")?,
205 allow_empty_cipher_values,
206 )?;
207 consume_encryption_properties(&mut children);
208 if children.next().is_some() {
209 return Err(XmlEncError::InvalidStructure(
210 "EncryptedData has unexpected child after CipherData".into(),
211 ));
212 }
213
214 let encrypted = EncryptedData {
215 id: bounded_attribute(node, "Id", policy)?,
216 encrypted_type: parse_encrypted_data_type(node.attribute("Type")),
217 key_name: key_info.key_name,
218 encryption_method,
219 encrypted_keys: key_info.encrypted_keys,
220 cipher_data,
221 };
222 validate_encrypted_data_metadata(&encrypted, policy)?;
223 Ok(encrypted)
224}
225
226fn parse_key_info(
227 node: Node<'_, '_>,
228 policy: ParsingPolicy<'_>,
229 allow_empty_cipher_values: bool,
230) -> Result<ParsedKeyInfo, XmlEncError> {
231 require_element(node, XMLDSIG_NS, "KeyInfo")?;
232 let mut key_name = None;
233 let mut encrypted_keys = Vec::new();
234 let mut unsupported_agreement = None;
235 for child in node.children().filter(Node::is_element) {
236 if child.has_tag_name((XMLDSIG_NS, "KeyName")) {
237 if key_name.is_some() {
238 return Err(XmlEncError::InvalidStructure(
239 "KeyInfo contains more than one direct KeyName".into(),
240 ));
241 }
242 key_name = Some(parse_key_name(child, policy)?);
243 } else if child.has_tag_name((XMLENC_NS, "EncryptedKey")) {
244 if encrypted_keys.len() == policy.resources.max_encryption_recipients {
245 return Err(crate::policy::PolicyViolation::ResourceLimit {
246 resource: crate::policy::resource_name::ENCRYPTION_RECIPIENTS,
247 maximum: policy.resources.max_encryption_recipients,
248 actual: encrypted_keys.len() + 1,
249 }
250 .into());
251 }
252 encrypted_keys.push(parse_encrypted_key(
253 child,
254 policy,
255 allow_empty_cipher_values,
256 )?);
257 } else if child.has_tag_name((XMLENC_NS, "AgreementMethod")) {
258 let algorithm = child
262 .attribute("Algorithm")
263 .ok_or(XmlEncError::MissingRequired(
264 "AgreementMethod Algorithm attribute",
265 ))?;
266 validate_metadata_len(
267 algorithm.len(),
268 policy.resources.max_encryption_metadata_bytes,
269 )?;
270 unsupported_agreement.get_or_insert_with(|| algorithm.to_owned());
271 }
272 }
273 if key_name.is_none()
274 && encrypted_keys.is_empty()
275 && let Some(algorithm) = unsupported_agreement
276 {
277 return Err(XmlEncError::UnsupportedAlgorithm(algorithm));
278 }
279 Ok(ParsedKeyInfo {
280 key_name,
281 encrypted_keys,
282 })
283}
284
285fn parse_encrypted_key(
286 node: Node<'_, '_>,
287 policy: ParsingPolicy<'_>,
288 allow_empty_cipher_values: bool,
289) -> Result<EncryptedKey, XmlEncError> {
290 require_element(node, XMLENC_NS, "EncryptedKey")?;
291 validate_encrypted_type_attributes(node, policy)?;
292 let mut children = element_children(node);
293 let encryption_method = parse_encryption_method_with_limit(
294 next_required(&mut children, "EncryptionMethod")?,
295 policy.resources.max_encryption_metadata_bytes,
296 )?;
297 if children
298 .peek()
299 .is_some_and(|child| child.has_tag_name((XMLENC_NS, "EncryptionMethod")))
300 {
301 return Err(XmlEncError::InvalidStructure(
302 "EncryptedKey contains more than one direct EncryptionMethod".into(),
303 ));
304 }
305 let key_name = if children
306 .peek()
307 .is_some_and(|child| child.has_tag_name((XMLDSIG_NS, "KeyInfo")))
308 {
309 parse_key_name_hint(next_required(&mut children, "KeyInfo")?, policy)?
310 } else {
311 None
312 };
313 if children
314 .peek()
315 .is_some_and(|child| child.has_tag_name((XMLDSIG_NS, "KeyInfo")))
316 {
317 return Err(XmlEncError::InvalidStructure(
318 "EncryptedKey contains more than one direct KeyInfo".into(),
319 ));
320 }
321 let cipher_data = parse_cipher_data(
322 next_required(&mut children, "CipherData")?,
323 allow_empty_cipher_values,
324 )?;
325 consume_encryption_properties(&mut children);
326 let reference_list = if children
327 .peek()
328 .is_some_and(|child| child.has_tag_name((XMLENC_NS, "ReferenceList")))
329 {
330 Some(parse_reference_list(
331 next_required(&mut children, "ReferenceList")?,
332 policy,
333 )?)
334 } else {
335 None
336 };
337 let carried_key_name = if children
338 .peek()
339 .is_some_and(|child| child.has_tag_name((XMLENC_NS, "CarriedKeyName")))
340 {
341 Some(parse_carried_key_name(
342 next_required(&mut children, "CarriedKeyName")?,
343 policy,
344 )?)
345 } else {
346 None
347 };
348 if children.next().is_some() {
349 return Err(XmlEncError::InvalidStructure(
350 "EncryptedKey has unexpected child after CipherData".into(),
351 ));
352 }
353 Ok(EncryptedKey {
354 id: bounded_attribute(node, "Id", policy)?,
355 recipient: bounded_attribute(node, "Recipient", policy)?,
356 key_name,
357 encryption_method,
358 cipher_data,
359 reference_list,
360 carried_key_name,
361 })
362}
363
364fn parse_carried_key_name(
365 node: Node<'_, '_>,
366 policy: ParsingPolicy<'_>,
367) -> Result<String, XmlEncError> {
368 require_element(node, XMLENC_NS, "CarriedKeyName")?;
369 let value = bounded_simple_text(node, "CarriedKeyName", policy)?;
370 if value.is_empty() {
371 return Err(XmlEncError::InvalidStructure(
372 "CarriedKeyName is empty".into(),
373 ));
374 }
375 Ok(value)
376}
377
378fn parse_key_name_hint(
379 node: Node<'_, '_>,
380 policy: ParsingPolicy<'_>,
381) -> Result<Option<String>, XmlEncError> {
382 require_element(node, XMLDSIG_NS, "KeyInfo")?;
383 let mut key_names = node
384 .children()
385 .filter(|child| child.has_tag_name((XMLDSIG_NS, "KeyName")));
386 let Some(key_name) = key_names.next() else {
387 return Ok(None);
388 };
389 if key_names.next().is_some() {
390 return Err(XmlEncError::InvalidStructure(
391 "EncryptedKey KeyInfo contains more than one direct KeyName".into(),
392 ));
393 }
394 parse_key_name(key_name, policy).map(Some)
395}
396
397fn parse_key_name(node: Node<'_, '_>, policy: ParsingPolicy<'_>) -> Result<String, XmlEncError> {
398 let value = bounded_simple_text(node, "KeyName", policy)?;
399 if value.is_empty() {
400 return Err(XmlEncError::InvalidStructure("KeyName is empty".into()));
401 }
402 Ok(value)
403}
404
405fn parse_reference_list(
406 node: Node<'_, '_>,
407 policy: ParsingPolicy<'_>,
408) -> Result<ReferenceList, XmlEncError> {
409 require_element(node, XMLENC_NS, "ReferenceList")?;
410 let mut data_references = Vec::new();
411 let mut key_references = Vec::new();
412 for child in node.children().filter(Node::is_element) {
413 let uri = child
414 .attribute("URI")
415 .filter(|uri| !uri.is_empty())
416 .ok_or(XmlEncError::MissingRequired("Reference URI attribute"))?;
417 validate_metadata_len(uri.len(), policy.resources.max_encryption_metadata_bytes)?;
418 let uri = uri.to_owned();
419 match (child.tag_name().namespace(), child.tag_name().name()) {
420 (Some(XMLENC_NS), "DataReference") => data_references.push(uri),
421 (Some(XMLENC_NS), "KeyReference") => key_references.push(uri),
422 _ => {
423 return Err(XmlEncError::InvalidStructure(format!(
424 "unsupported ReferenceList child {}",
425 child.tag_name().name()
426 )));
427 }
428 }
429 }
430 if data_references.is_empty() && key_references.is_empty() {
431 return Err(XmlEncError::InvalidStructure(
432 "ReferenceList must contain at least one reference".into(),
433 ));
434 }
435 Ok(ReferenceList {
436 data_references,
437 key_references,
438 })
439}
440
441fn consume_encryption_properties<'a, I>(children: &mut std::iter::Peekable<I>)
442where
443 I: Iterator<Item = Node<'a, 'a>>,
444{
445 if children
446 .peek()
447 .is_some_and(|child| child.has_tag_name((XMLENC_NS, "EncryptionProperties")))
448 {
449 let _ = children.next();
450 }
451}
452
453#[cfg(test)]
454fn parse_encryption_method(node: Node<'_, '_>) -> Result<EncryptionMethod, XmlEncError> {
455 parse_encryption_method_with_limit(node, crate::hard_limits::ENCRYPTION_METADATA_BYTE_CEILING)
456}
457
458fn parse_encryption_method_with_limit(
459 node: Node<'_, '_>,
460 metadata_limit: usize,
461) -> Result<EncryptionMethod, XmlEncError> {
462 require_element(node, XMLENC_NS, "EncryptionMethod")?;
463 let algorithm = node
464 .attribute("Algorithm")
465 .ok_or(XmlEncError::MissingRequired(
466 "EncryptionMethod Algorithm attribute",
467 ))?;
468 validate_metadata_len(algorithm.len(), metadata_limit)?;
469 let algorithm = algorithm.to_owned();
470
471 let mut oaep_digest = None;
472 let mut mgf_algorithm = None;
473 let mut oaep_params = None;
474 let mut key_size_bits = None;
475 for child in node.children().filter(Node::is_element) {
476 match (child.tag_name().namespace(), child.tag_name().name()) {
477 (Some(XMLENC_NS), "KeySize")
478 if key_size_bits.is_none()
479 && oaep_params.is_none()
480 && oaep_digest.is_none()
481 && mgf_algorithm.is_none() =>
482 {
483 key_size_bits = Some(parse_key_size(child, metadata_limit)?);
484 }
485 (Some(XMLENC_NS), "OAEPparams") if oaep_params.is_none() => {
486 oaep_params = Some(decode_bounded_base64_text(child, metadata_limit)?);
487 }
488 (Some(XMLDSIG_NS), "DigestMethod") if oaep_digest.is_none() => {
489 let digest = child
490 .attribute("Algorithm")
491 .ok_or(XmlEncError::MissingRequired(
492 "DigestMethod Algorithm attribute",
493 ))?;
494 validate_metadata_len(digest.len(), metadata_limit)?;
495 oaep_digest = Some(digest.to_owned());
496 }
497 (Some(XMLENC11_NS), "MGF") if mgf_algorithm.is_none() => {
498 let mgf = child
499 .attribute("Algorithm")
500 .ok_or(XmlEncError::MissingRequired("MGF Algorithm attribute"))?;
501 validate_metadata_len(mgf.len(), metadata_limit)?;
502 mgf_algorithm = Some(mgf.to_owned());
503 }
504 _ => {
505 return Err(XmlEncError::InvalidStructure(format!(
506 "unsupported EncryptionMethod child {}",
507 child.tag_name().name()
508 )));
509 }
510 }
511 }
512
513 let method = EncryptionMethod {
514 algorithm,
515 key_size_bits,
516 oaep_digest,
517 mgf_algorithm,
518 oaep_params,
519 };
520 method.validate_structure()?;
521 Ok(method)
522}
523
524fn parse_key_size(node: Node<'_, '_>, metadata_limit: usize) -> Result<usize, XmlEncError> {
525 let value = bounded_simple_text_with_limit(node, "KeySize", metadata_limit)?;
526 let value = value.trim();
527 let bits = value
528 .parse::<usize>()
529 .map_err(|_| XmlEncError::InvalidStructure("KeySize must be a positive integer".into()))?;
530 if bits == 0 {
531 return Err(XmlEncError::InvalidStructure(
532 "KeySize must be a positive integer".into(),
533 ));
534 }
535 Ok(bits)
536}
537
538fn parse_cipher_data(node: Node<'_, '_>, allow_empty: bool) -> Result<CipherData, XmlEncError> {
539 require_element(node, XMLENC_NS, "CipherData")?;
540 let mut children = element_children(node);
541 let value = next_required(&mut children, "CipherValue")?;
542 require_element(value, XMLENC_NS, "CipherValue")?;
543 if children.next().is_some() {
544 return Err(XmlEncError::InvalidStructure(
545 "CipherData must contain exactly one CipherValue".into(),
546 ));
547 }
548 Ok(CipherData {
549 value: normalize_base64_with_empty(&simple_text(value, "CipherValue")?, allow_empty)?,
550 })
551}
552
553fn simple_text(node: Node<'_, '_>, element_name: &str) -> Result<String, XmlEncError> {
554 if node.children().any(|child| child.is_element()) {
555 return Err(XmlEncError::InvalidStructure(format!(
556 "{element_name} must not contain element children"
557 )));
558 }
559 Ok(node
560 .children()
561 .filter(Node::is_text)
562 .filter_map(|child| child.text())
563 .collect())
564}
565
566fn bounded_simple_text(
567 node: Node<'_, '_>,
568 field: &'static str,
569 policy: ParsingPolicy<'_>,
570) -> Result<String, XmlEncError> {
571 bounded_simple_text_with_limit(node, field, policy.resources.max_encryption_metadata_bytes)
572}
573
574fn bounded_simple_text_with_limit(
575 node: Node<'_, '_>,
576 field: &'static str,
577 maximum: usize,
578) -> Result<String, XmlEncError> {
579 if node.children().any(|child| child.is_element()) {
580 return Err(XmlEncError::InvalidStructure(format!(
581 "{field} must not contain element children"
582 )));
583 }
584 let mut value = String::new();
585 for text in node
586 .children()
587 .filter(Node::is_text)
588 .filter_map(|child| child.text())
589 {
590 let actual = value.len().saturating_add(text.len());
591 validate_metadata_len(actual, maximum)?;
592 value.push_str(text);
593 }
594 Ok(value)
595}
596
597fn bounded_attribute(
598 node: Node<'_, '_>,
599 attribute: &str,
600 policy: ParsingPolicy<'_>,
601) -> Result<Option<String>, XmlEncError> {
602 let Some(value) = node.attribute(attribute) else {
603 return Ok(None);
604 };
605 validate_metadata_len(value.len(), policy.resources.max_encryption_metadata_bytes)?;
606 Ok(Some(value.to_owned()))
607}
608
609fn validate_metadata_len(actual: usize, maximum: usize) -> Result<(), XmlEncError> {
610 if actual <= maximum {
611 Ok(())
612 } else {
613 Err(crate::policy::PolicyViolation::ResourceLimit {
614 resource: crate::policy::resource_name::ENCRYPTION_METADATA_BYTES,
615 maximum,
616 actual,
617 }
618 .into())
619 }
620}
621
622pub(super) fn validate_encrypted_data_metadata<'a>(
623 encrypted: &EncryptedData,
624 policy: impl Into<ParsingPolicy<'a>>,
625) -> Result<(), XmlEncError> {
626 let policy = policy.into();
627 let maximum = policy.resources.max_encryption_metadata_bytes;
628 let validate = |value: Option<&str>| validate_metadata_len(value.map_or(0, str::len), maximum);
629 validate(encrypted.id.as_deref())?;
630 if let Some(encrypted_type) = encrypted.encrypted_type.as_ref() {
631 let value = match encrypted_type {
632 EncryptedDataType::Element => "http://www.w3.org/2001/04/xmlenc#Element",
633 EncryptedDataType::Content => "http://www.w3.org/2001/04/xmlenc#Content",
634 EncryptedDataType::Other(value) => value,
635 };
636 validate(Some(value))?;
637 }
638 validate(encrypted.key_name.as_deref())?;
639 validate_encryption_method_metadata(&encrypted.encryption_method, maximum)?;
640 for key in &encrypted.encrypted_keys {
641 validate(key.id.as_deref())?;
642 validate(key.recipient.as_deref())?;
643 validate(key.key_name.as_deref())?;
644 validate(key.carried_key_name.as_deref())?;
645 validate_encryption_method_metadata(&key.encryption_method, maximum)?;
646 if let Some(references) = key.reference_list.as_ref() {
647 for uri in references
648 .data_references
649 .iter()
650 .chain(&references.key_references)
651 {
652 validate(Some(uri))?;
653 }
654 }
655 }
656 Ok(())
657}
658
659fn validate_encryption_method_metadata(
660 method: &EncryptionMethod,
661 maximum: usize,
662) -> Result<(), XmlEncError> {
663 validate_metadata_len(method.algorithm.len(), maximum)?;
664 if let Some(value) = method.oaep_digest.as_deref() {
665 validate_metadata_len(value.len(), maximum)?;
666 }
667 if let Some(value) = method.mgf_algorithm.as_deref() {
668 validate_metadata_len(value.len(), maximum)?;
669 }
670 if let Some(value) = method.oaep_params.as_deref() {
671 validate_metadata_len(value.len(), maximum)?;
672 }
673 Ok(())
674}
675
676fn validate_encrypted_type_attributes(
677 node: Node<'_, '_>,
678 policy: ParsingPolicy<'_>,
679) -> Result<(), XmlEncError> {
680 bounded_attribute(node, "Type", policy)?;
684 bounded_attribute(node, "MimeType", policy)?;
685 bounded_attribute(node, "Encoding", policy)?;
686 Ok(())
687}
688
689fn parse_encrypted_data_type(value: Option<&str>) -> Option<EncryptedDataType> {
690 value.map(|value| match value {
691 "http://www.w3.org/2001/04/xmlenc#Element" => EncryptedDataType::Element,
692 "http://www.w3.org/2001/04/xmlenc#Content" => EncryptedDataType::Content,
693 other => EncryptedDataType::Other(other.to_owned()),
694 })
695}
696
697fn decode_bounded_base64_text(node: Node<'_, '_>, maximum: usize) -> Result<Vec<u8>, XmlEncError> {
698 if node.children().any(|child| child.is_element()) {
699 return Err(XmlEncError::InvalidStructure(
700 "OAEPparams must not contain element children".into(),
701 ));
702 }
703 let encoded_limit = maximum.div_ceil(3).saturating_mul(4);
704 let mut normalized = String::with_capacity(encoded_limit);
705 for character in node
706 .children()
707 .filter(Node::is_text)
708 .filter_map(|child| child.text())
709 .flat_map(str::chars)
710 {
711 if !character.is_ascii() {
712 return Err(XmlEncError::Base64(
713 "OAEPparams contains non-ASCII data".into(),
714 ));
715 }
716 if !character.is_ascii_whitespace() {
717 if normalized.len() == encoded_limit {
718 return Err(crate::policy::PolicyViolation::ResourceLimit {
719 resource: crate::policy::resource_name::ENCRYPTION_METADATA_BYTES,
720 maximum,
721 actual: maximum.saturating_add(1),
722 }
723 .into());
724 }
725 normalized.push(character);
726 }
727 }
728 let decoded = STANDARD
729 .decode(normalized)
730 .map_err(|error| XmlEncError::Base64(error.to_string()))?;
731 validate_metadata_len(decoded.len(), maximum)?;
732 Ok(decoded)
733}
734
735fn normalize_base64_with_empty(value: &str, allow_empty: bool) -> Result<String, XmlEncError> {
736 let mut normalized = String::with_capacity(value.len().min(MAX_CIPHER_VALUE_BASE64_LEN));
737 for character in value.chars() {
738 if !character.is_ascii() {
739 return Err(XmlEncError::Base64(
740 "CipherValue contains non-ASCII data".into(),
741 ));
742 }
743 if !character.is_ascii_whitespace() {
744 if normalized.len() == MAX_CIPHER_VALUE_BASE64_LEN {
745 return Err(XmlEncError::Base64(format!(
746 "CipherValue exceeds {MAX_CIPHER_VALUE_BASE64_LEN}-byte limit"
747 )));
748 }
749 normalized.push(character);
750 }
751 }
752 if normalized.is_empty() && !allow_empty {
753 return Err(XmlEncError::Base64("CipherValue is empty".into()));
754 }
755 if !normalized.is_empty() {
756 STANDARD
757 .decode(&normalized)
758 .map_err(|error| XmlEncError::Base64(error.to_string()))?;
759 }
760 Ok(normalized)
761}
762
763#[cfg(test)]
764fn normalize_base64(value: &str) -> Result<String, XmlEncError> {
765 normalize_base64_with_empty(value, false)
766}
767
768fn require_element(node: Node<'_, '_>, namespace: &str, name: &str) -> Result<(), XmlEncError> {
769 if node.has_tag_name((namespace, name)) {
770 Ok(())
771 } else {
772 Err(XmlEncError::InvalidStructure(format!(
773 "expected {{{namespace}}}{name}"
774 )))
775 }
776}
777
778fn element_children<'a>(
779 node: Node<'a, 'a>,
780) -> std::iter::Peekable<impl Iterator<Item = Node<'a, 'a>>> {
781 node.children().filter(Node::is_element).peekable()
782}
783
784fn next_required<'a, I>(
785 children: &mut std::iter::Peekable<I>,
786 expected: &'static str,
787) -> Result<Node<'a, 'a>, XmlEncError>
788where
789 I: Iterator<Item = Node<'a, 'a>>,
790{
791 children
792 .next()
793 .ok_or(XmlEncError::MissingRequired(expected))
794}
795
796#[cfg(test)]
797mod tests {
798 use super::*;
799
800 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>";
801
802 #[test]
803 fn parses_supported_encrypted_data_and_normalizes_cipher_value() {
804 let parsed = parse_encrypted_data(DATA).expect("valid XMLEnc data must parse");
806 assert_eq!(parsed.cipher_data.value, "YWJjZA==");
807 assert_eq!(parsed.encrypted_type, Some(EncryptedDataType::Element));
808 }
809
810 #[cfg(feature = "xml-backend-differential")]
811 #[test]
812 fn node_revalidation_uses_the_operation_backend() {
813 let backend = crate::XmlBackend::Roxmltree;
816 let document = Document::parse_with_backend(DATA, backend).expect("test XML must parse");
817 let expected_work = DATA.len() * 3;
818 let resources = crate::policy::ResourcePolicy {
819 max_xml_parse_work_bytes: expected_work,
820 ..crate::policy::ResourcePolicy::default()
821 };
822 let policy = crate::policy::DecryptionPolicy {
823 resources: resources.clone(),
824 ..crate::policy::DecryptionPolicy::default()
825 };
826 let budget = XmlParseWorkBudget::from_resources(&resources);
827
828 parse_encrypted_data_node_with_policy_and_budget(
829 document.root_element(),
830 &policy,
831 &budget,
832 backend,
833 )
834 .expect("node revalidation must retain the selected backend");
835 assert_eq!(budget.consumed(), expected_work);
836
837 parse_encrypted_data_node_with_policy_and_backend(
838 document.root_element(),
839 &policy,
840 backend,
841 )
842 .expect("public decryption parser must retain the selected backend");
843 let encryption_policy = crate::policy::EncryptionPolicy {
844 resources,
845 ..crate::policy::EncryptionPolicy::default()
846 };
847 parse_encrypted_data_template_node_with_policy_and_backend(
848 document.root_element(),
849 &encryption_policy,
850 backend,
851 )
852 .expect("public template parser must retain the selected backend");
853 }
854
855 #[test]
856 fn node_parsers_enforce_the_containing_document_byte_limit() {
857 let containing = format!("<root>{}<payload/></root>", DATA);
860 let document = Document::parse(&containing).expect("containing document must parse");
861 let encrypted_data = document
862 .descendants()
863 .find(|node| node.has_tag_name((XMLENC_NS, "EncryptedData")))
864 .expect("selected EncryptedData");
865 let resources = crate::policy::ResourcePolicy {
866 max_xml_document_bytes: DATA.len(),
867 ..crate::policy::ResourcePolicy::default()
868 };
869 let decryption = crate::policy::DecryptionPolicy {
870 resources: resources.clone(),
871 ..crate::policy::DecryptionPolicy::default()
872 };
873 let encryption = crate::policy::EncryptionPolicy {
874 resources,
875 ..crate::policy::EncryptionPolicy::default()
876 };
877
878 for result in [
879 parse_encrypted_data_node_with_policy(encrypted_data, &decryption),
880 parse_encrypted_data_template_node_with_policy(encrypted_data, &encryption),
881 ] {
882 assert!(matches!(
883 result,
884 Err(XmlEncError::Policy(
885 crate::policy::PolicyViolation::ResourceLimit {
886 resource: crate::policy::resource_name::XML_DOCUMENT,
887 ..
888 }
889 ))
890 ));
891 }
892 }
893
894 #[test]
895 fn node_parsers_enforce_the_containing_document_node_limit() {
896 let containing = format!("<root>{}<payload/></root>", DATA);
899 let document = Document::parse(&containing).expect("containing document must parse");
900 let encrypted_data = document
901 .descendants()
902 .find(|node| node.has_tag_name((XMLENC_NS, "EncryptedData")))
903 .expect("selected EncryptedData");
904 let actual_nodes = document.root().descendants().count();
905 let resources = crate::policy::ResourcePolicy {
906 max_xml_nodes: actual_nodes - 1,
907 ..crate::policy::ResourcePolicy::default()
908 };
909 let decryption = crate::policy::DecryptionPolicy {
910 resources: resources.clone(),
911 ..crate::policy::DecryptionPolicy::default()
912 };
913 let encryption = crate::policy::EncryptionPolicy {
914 resources,
915 ..crate::policy::EncryptionPolicy::default()
916 };
917
918 for result in [
919 parse_encrypted_data_node_with_policy(encrypted_data, &decryption),
920 parse_encrypted_data_template_node_with_policy(encrypted_data, &encryption),
921 ] {
922 assert!(matches!(
923 result,
924 Err(XmlEncError::Policy(
925 crate::policy::PolicyViolation::ResourceLimit {
926 resource: "XML nodes",
927 maximum,
928 actual,
929 }
930 )) if maximum == actual_nodes - 1 && actual == actual_nodes
931 ));
932 }
933
934 let exact_resources = crate::policy::ResourcePolicy {
935 max_xml_nodes: actual_nodes,
936 ..crate::policy::ResourcePolicy::default()
937 };
938 let exact_policy = crate::policy::DecryptionPolicy {
939 resources: exact_resources,
940 ..crate::policy::DecryptionPolicy::default()
941 };
942 parse_encrypted_data_node_with_policy(encrypted_data, &exact_policy)
943 .expect("a document exactly at the node ceiling must parse");
944 }
945
946 #[test]
947 fn policy_parsers_enforce_the_complete_document_depth() {
948 let containing = format!("<outer><inner>{DATA}</inner></outer>");
951 let document = Document::parse(&containing).expect("containing document must parse");
952 let encrypted_data = document
953 .descendants()
954 .find(|node| node.has_tag_name((XMLENC_NS, "EncryptedData")))
955 .expect("selected EncryptedData");
956 let actual_depth = encrypted_data
957 .document()
958 .descendants()
959 .filter(|node| node.is_element())
960 .map(|node| {
961 node.ancestors()
962 .filter(|ancestor| ancestor.is_element())
963 .count()
964 })
965 .max()
966 .expect("fixture has elements");
967 let resources = crate::policy::ResourcePolicy {
968 max_xml_depth: actual_depth - 1,
969 ..crate::policy::ResourcePolicy::default()
970 };
971 let decryption = crate::policy::DecryptionPolicy {
972 resources: resources.clone(),
973 ..crate::policy::DecryptionPolicy::default()
974 };
975 let encryption = crate::policy::EncryptionPolicy {
976 resources,
977 ..crate::policy::EncryptionPolicy::default()
978 };
979
980 for result in [
981 parse_encrypted_data_node_with_policy(encrypted_data, &decryption),
982 parse_encrypted_data_template_node_with_policy(encrypted_data, &encryption),
983 ] {
984 assert!(matches!(
985 result,
986 Err(XmlEncError::Policy(
987 crate::policy::PolicyViolation::ResourceLimit {
988 resource: crate::policy::resource_name::XML_DEPTH,
989 maximum,
990 actual,
991 }
992 )) if maximum == actual_depth - 1 && actual == actual_depth
993 ));
994 }
995 }
996
997 #[test]
998 fn node_parsers_revalidate_the_containing_documents_dtd_policy() {
999 let containing = format!(
1002 r#"<!DOCTYPE root [<!ENTITY marker "allowed">]><root>{DATA}<payload>▮</payload></root>"#
1003 );
1004 let document = Document::parse_with_options(
1005 &containing,
1006 ParsingOptions {
1007 allow_dtd: true,
1008 ..ParsingOptions::default()
1009 },
1010 )
1011 .expect("the caller can parse a document under a more permissive policy");
1012 let encrypted_data = document
1013 .descendants()
1014 .find(|node| node.has_tag_name((XMLENC_NS, "EncryptedData")))
1015 .expect("selected EncryptedData");
1016
1017 for result in [
1018 parse_encrypted_data_node_with_policy(
1019 encrypted_data,
1020 &crate::policy::DecryptionPolicy::default(),
1021 ),
1022 parse_encrypted_data_template_node_with_policy(
1023 encrypted_data,
1024 &crate::policy::EncryptionPolicy::default(),
1025 ),
1026 ] {
1027 assert!(matches!(result, Err(XmlEncError::XmlParse(_))));
1028 }
1029
1030 let mut decryption_allowed = crate::policy::DecryptionPolicy::default();
1031 decryption_allowed.xml.allow_internal_dtd = true;
1032 parse_encrypted_data_node_with_policy(encrypted_data, &decryption_allowed)
1033 .expect("explicitly permitted internal DTD must remain accepted");
1034 let mut encryption_allowed = crate::policy::EncryptionPolicy::default();
1035 encryption_allowed.xml.allow_internal_dtd = true;
1036 parse_encrypted_data_template_node_with_policy(encrypted_data, &encryption_allowed)
1037 .expect("template parsing must share the same explicit DTD policy");
1038 }
1039
1040 #[test]
1041 fn template_parser_rejects_nonempty_invalid_cipher_values() {
1042 let invalid_direct = DATA.replace(" YWJj\nZA== ", "!!!!");
1045 let invalid_recipient = format!(
1046 "<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>"
1047 );
1048 for xml in [&invalid_direct, &invalid_recipient] {
1049 let document = Document::parse(xml).expect("template must be well-formed XML");
1050 assert!(matches!(
1051 parse_encrypted_data_template_node_with_policy(
1052 document.root_element(),
1053 &crate::policy::EncryptionPolicy::default(),
1054 ),
1055 Err(XmlEncError::Base64(_))
1056 ));
1057 }
1058
1059 let empty = DATA.replace(" YWJj\nZA== ", "");
1060 let document = Document::parse(&empty).expect("empty template must be XML");
1061 parse_encrypted_data_template_node_with_policy(
1062 document.root_element(),
1063 &crate::policy::EncryptionPolicy::default(),
1064 )
1065 .expect("an explicit empty template placeholder remains valid");
1066 }
1067
1068 #[test]
1069 fn rejects_cipher_reference_and_trailing_children() {
1070 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>";
1072 assert!(
1073 parse_encrypted_data(xml).is_err(),
1074 "CipherReference must fail closed"
1075 );
1076 }
1077
1078 #[test]
1079 fn joins_comment_split_cipher_text_and_rejects_element_children() {
1080 let split = DATA.replace("YWJj\nZA==", "YW<!-- split -->Jj\nZA==");
1083 let parsed = parse_encrypted_data(&split).expect("comment-split base64 must parse");
1084 assert_eq!(parsed.cipher_data.value, "YWJjZA==");
1085
1086 let nested = DATA.replace("YWJj\nZA==", "YW<xenc:Unexpected/>JjZA==");
1087 assert!(matches!(
1088 parse_encrypted_data(&nested),
1089 Err(XmlEncError::InvalidStructure(_))
1090 ));
1091 }
1092
1093 #[test]
1094 fn rejects_wrong_namespaces_and_retains_recipient_keys() {
1095 let wrong_namespace = DATA.replace(XMLENC_NS, "urn:not-xmlenc");
1098 assert!(matches!(
1099 parse_encrypted_data(&wrong_namespace),
1100 Err(XmlEncError::InvalidStructure(_))
1101 ));
1102
1103 let encrypted_key = |recipient: &str| {
1104 format!(
1105 "<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>"
1106 )
1107 };
1108 let recipients = format!(
1109 "<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>",
1110 encrypted_key("alice"),
1111 encrypted_key("bob")
1112 );
1113 let parsed = parse_encrypted_data(&recipients).expect("recipient keys must parse");
1114 assert_eq!(
1115 parsed
1116 .encrypted_keys
1117 .iter()
1118 .filter_map(|key| key.recipient.as_deref())
1119 .collect::<Vec<_>>(),
1120 ["alice", "bob"]
1121 );
1122 }
1123
1124 #[test]
1126 fn rejects_unsupported_key_agreement_explicitly() {
1127 let xml = format!(
1130 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>"#
1131 );
1132 assert!(matches!(
1133 parse_encrypted_data(&xml),
1134 Err(XmlEncError::UnsupportedAlgorithm(uri))
1135 if uri == "http://www.w3.org/2001/04/xmlenc#dh"
1136 ));
1137
1138 let missing_algorithm =
1139 xml.replace(" Algorithm=\"http://www.w3.org/2001/04/xmlenc#dh\"", "");
1140 assert!(matches!(
1141 parse_encrypted_data(&missing_algorithm),
1142 Err(XmlEncError::MissingRequired(
1143 "AgreementMethod Algorithm attribute"
1144 ))
1145 ));
1146 }
1147
1148 #[test]
1150 fn retains_supported_key_candidates_alongside_unsupported_agreement() {
1151 let xml = format!(
1154 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>"#
1155 );
1156
1157 let parsed = parse_encrypted_data(&xml)
1158 .expect("a supported key candidate must take precedence over agreement fallback");
1159 assert_eq!(parsed.key_name.as_deref(), Some("content-key"));
1160 assert_eq!(parsed.encrypted_keys.len(), 1);
1161 assert_eq!(parsed.encrypted_keys[0].recipient.as_deref(), Some("alice"));
1162 }
1163
1164 #[test]
1165 fn rejects_missing_algorithm_and_duplicate_oaep_parameters() {
1166 let missing_algorithm = DATA.replace(
1169 " Algorithm=\"http://www.w3.org/2009/xmlenc11#aes128-gcm\"",
1170 "",
1171 );
1172 assert!(matches!(
1173 parse_encrypted_data(&missing_algorithm),
1174 Err(XmlEncError::MissingRequired(_))
1175 ));
1176
1177 let duplicate_oaep = format!(
1178 "<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>"
1179 );
1180 assert!(matches!(
1181 parse_encrypted_data(&duplicate_oaep),
1182 Err(XmlEncError::InvalidStructure(_))
1183 ));
1184
1185 let oaep_on_aes = DATA.replace(
1186 "/><xenc:CipherData>",
1187 "><xenc:OAEPparams>YQ==</xenc:OAEPparams></xenc:EncryptionMethod><xenc:CipherData>",
1188 );
1189 assert!(matches!(
1190 parse_encrypted_data(&oaep_on_aes),
1191 Err(XmlEncError::InvalidStructure(_))
1192 ));
1193 }
1194
1195 #[test]
1196 fn accepts_empty_oaep_params_as_an_explicit_empty_label() {
1197 for params in ["", " \n\t "] {
1200 let xml = format!(
1201 "<xenc:EncryptionMethod xmlns:xenc=\"{XMLENC_NS}\" Algorithm=\"http://www.w3.org/2009/xmlenc11#rsa-oaep\"><xenc:OAEPparams>{params}</xenc:OAEPparams></xenc:EncryptionMethod>"
1202 );
1203 let document = Document::parse(&xml).expect("test method must be XML");
1204 let parsed = parse_encryption_method(document.root_element())
1205 .expect("empty OAEPparams must decode as an empty label");
1206 assert_eq!(parsed.oaep_params, Some(Vec::new()));
1207 }
1208
1209 assert!(matches!(
1210 normalize_base64(" \n\t "),
1211 Err(XmlEncError::Base64(_))
1212 ));
1213 }
1214
1215 #[test]
1216 fn bounds_oaep_parameters_before_base64_allocation() {
1217 let xml = format!(
1220 "<xenc:EncryptionMethod xmlns:xenc=\"{XMLENC_NS}\" Algorithm=\"http://www.w3.org/2009/xmlenc11#rsa-oaep\"><xenc:OAEPparams>{}</xenc:OAEPparams></xenc:EncryptionMethod>",
1221 STANDARD.encode([0_u8; 65])
1222 );
1223 let document = Document::parse(&xml).expect("test method must be XML");
1224
1225 assert!(matches!(
1226 parse_encryption_method_with_limit(document.root_element(), 64),
1227 Err(XmlEncError::Policy(
1228 crate::policy::PolicyViolation::ResourceLimit {
1229 resource: crate::policy::resource_name::ENCRYPTION_METADATA_BYTES,
1230 maximum: 64,
1231 actual: 65,
1232 }
1233 ))
1234 ));
1235 }
1236
1237 #[test]
1238 fn validates_explicit_key_size_for_supported_aes_methods() {
1239 for (algorithm, bits) in [
1242 ("http://www.w3.org/2001/04/xmlenc#aes128-cbc", 128),
1243 ("http://www.w3.org/2001/04/xmlenc#aes256-cbc", 256),
1244 ("http://www.w3.org/2009/xmlenc11#aes128-gcm", 128),
1245 ("http://www.w3.org/2009/xmlenc11#aes256-gcm", 256),
1246 ("http://www.w3.org/2001/04/xmlenc#kw-aes128", 128),
1247 ("http://www.w3.org/2001/04/xmlenc#kw-aes256", 256),
1248 ] {
1249 let xml = format!(
1250 "<xenc:EncryptionMethod xmlns:xenc=\"{XMLENC_NS}\" Algorithm=\"{algorithm}\"><xenc:KeySize>{bits}</xenc:KeySize></xenc:EncryptionMethod>"
1251 );
1252 let document = Document::parse(&xml).expect("test method must be XML");
1253 let parsed = parse_encryption_method(document.root_element())
1254 .expect("matching AES KeySize must parse");
1255 assert_eq!(parsed.key_size_bits, Some(bits));
1256
1257 let inconsistent = xml.replace(&format!(">{bits}<"), ">192<");
1258 let document = Document::parse(&inconsistent).expect("test method must be XML");
1259 assert!(matches!(
1260 parse_encryption_method(document.root_element()),
1261 Err(XmlEncError::InvalidStructure(_))
1262 ));
1263 }
1264
1265 for key_size in ["128.0", "", "128</xenc:KeySize><xenc:KeySize>128"] {
1266 let xml = format!(
1267 "<xenc:EncryptionMethod xmlns:xenc=\"{XMLENC_NS}\" Algorithm=\"http://www.w3.org/2009/xmlenc11#aes128-gcm\"><xenc:KeySize>{key_size}</xenc:KeySize></xenc:EncryptionMethod>"
1268 );
1269 let document = Document::parse(&xml).expect("test method must be XML");
1270 assert!(matches!(
1271 parse_encryption_method(document.root_element()),
1272 Err(XmlEncError::InvalidStructure(_))
1273 ));
1274 }
1275 }
1276
1277 #[test]
1278 fn key_size_text_is_bounded_before_integer_parsing() {
1279 let key_size = format!("{}128", "0".repeat(65));
1282 let xml = format!(
1283 "<xenc:EncryptionMethod xmlns:xenc=\"{XMLENC_NS}\" Algorithm=\"http://www.w3.org/2009/xmlenc11#aes128-gcm\"><xenc:KeySize>{key_size}</xenc:KeySize></xenc:EncryptionMethod>"
1284 );
1285 let document = Document::parse(&xml).expect("test method must be XML");
1286
1287 assert!(matches!(
1288 parse_encryption_method_with_limit(document.root_element(), 64),
1289 Err(XmlEncError::Policy(
1290 crate::policy::PolicyViolation::ResourceLimit {
1291 resource: crate::policy::resource_name::ENCRYPTION_METADATA_BYTES,
1292 maximum: 64,
1293 actual: 68,
1294 }
1295 ))
1296 ));
1297 }
1298
1299 #[test]
1300 fn retains_key_names_and_encrypted_key_reference_list() {
1301 let xml = format!(
1304 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>"##
1305 );
1306 let parsed = parse_encrypted_data(&xml).expect("complete key metadata must parse");
1307 assert_eq!(parsed.key_name.as_deref(), Some("content-key"));
1308 let encrypted_key = parsed
1309 .encrypted_keys
1310 .first()
1311 .expect("embedded key must be retained");
1312 assert_eq!(encrypted_key.key_name.as_deref(), Some("wrapping-key"));
1313 let references = encrypted_key
1314 .reference_list
1315 .as_ref()
1316 .expect("reference list must be retained");
1317 assert_eq!(references.data_references, ["#data-1"]);
1318 assert_eq!(references.key_references, ["#key-2"]);
1319 }
1320
1321 #[test]
1322 fn preserves_key_identifier_whitespace() {
1323 let xml = format!(
1326 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>"#
1327 );
1328 let parsed = parse_encrypted_data(&xml).expect("key metadata must parse");
1329 assert_eq!(parsed.key_name.as_deref(), Some(" content-key "));
1330 let encrypted_key = parsed
1331 .encrypted_keys
1332 .first()
1333 .expect("embedded key must be retained");
1334 assert_eq!(encrypted_key.key_name.as_deref(), Some(" wrapping-key "));
1335 assert_eq!(
1336 encrypted_key.carried_key_name.as_deref(),
1337 Some(" transported-key ")
1338 );
1339 }
1340
1341 #[test]
1342 fn accepts_one_carried_key_name_and_rejects_duplicates() {
1343 let xml = format!(
1346 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>"##
1347 );
1348 let parsed = parse_encrypted_data(&xml).expect("one CarriedKeyName must parse");
1349 assert_eq!(
1350 parsed
1351 .encrypted_keys
1352 .first()
1353 .expect("embedded key must be retained")
1354 .carried_key_name
1355 .as_deref(),
1356 Some("transported-key")
1357 );
1358
1359 let duplicate = xml.replace(
1360 "</xenc:EncryptedKey>",
1361 "<xenc:CarriedKeyName>duplicate</xenc:CarriedKeyName></xenc:EncryptedKey>",
1362 );
1363 assert!(matches!(
1364 parse_encrypted_data(&duplicate),
1365 Err(XmlEncError::InvalidStructure(_))
1366 ));
1367 }
1368
1369 #[test]
1370 fn accepts_encrypted_key_key_info_without_key_name() {
1371 let xml = format!(
1374 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>"#
1375 );
1376 let parsed = parse_encrypted_data(&xml).expect("certificate-only KeyInfo must parse");
1377 assert_eq!(
1378 parsed
1379 .encrypted_keys
1380 .first()
1381 .expect("embedded key must be retained")
1382 .key_name
1383 .as_deref(),
1384 None
1385 );
1386 }
1387
1388 #[test]
1389 fn rejects_malformed_encrypted_key_reference_lists() {
1390 let template = format!(
1393 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>"#
1394 );
1395 for malformed in [
1396 "<xenc:ReferenceList/>",
1397 "<xenc:ReferenceList><xenc:DataReference/></xenc:ReferenceList>",
1398 "<xenc:ReferenceList><xenc:Unexpected URI=\"#data\"/></xenc:ReferenceList>",
1399 ] {
1400 let xml = template.replace("{reference_list}", malformed);
1401 assert!(
1402 parse_encrypted_data(&xml).is_err(),
1403 "malformed list must fail: {malformed}"
1404 );
1405 }
1406 }
1407
1408 #[test]
1409 fn bounds_normalized_cipher_value_before_decode() {
1410 let oversized = "A".repeat(MAX_CIPHER_VALUE_BASE64_LEN + 1);
1413 assert!(matches!(
1414 normalize_base64(&oversized),
1415 Err(XmlEncError::Base64(_))
1416 ));
1417 }
1418
1419 #[test]
1420 fn policy_bounds_copied_encryption_metadata() {
1421 let policy = crate::policy::DecryptionPolicy {
1424 resources: crate::policy::ResourcePolicy {
1425 max_encryption_metadata_bytes: 64,
1426 ..crate::policy::ResourcePolicy::default()
1427 },
1428 ..crate::policy::DecryptionPolicy::default()
1429 };
1430 let oversized = "x".repeat(65);
1431 for xml in [
1432 DATA.replace("<xenc:EncryptedData ", &format!("<xenc:EncryptedData Id=\"{oversized}\" ")),
1433 DATA.replace(
1434 "<xenc:EncryptedData ",
1435 &format!("<xenc:EncryptedData MimeType=\"{oversized}\" "),
1436 ),
1437 DATA.replace(
1438 "<xenc:EncryptedData ",
1439 &format!("<xenc:EncryptedData Encoding=\"{oversized}\" "),
1440 ),
1441 DATA.replace(
1442 "<xenc:CipherData>",
1443 &format!("<ds:KeyInfo xmlns:ds=\"{XMLDSIG_NS}\"><ds:KeyName>{oversized}</ds:KeyName></ds:KeyInfo><xenc:CipherData>"),
1444 ),
1445 ] {
1446 assert!(matches!(
1447 parse_encrypted_data_with_policy(&xml, &policy),
1448 Err(XmlEncError::Policy(crate::policy::PolicyViolation::ResourceLimit {
1449 maximum: 64,
1450 actual: 65,
1451 ..
1452 }))
1453 ));
1454 }
1455 }
1456
1457 #[test]
1458 fn policy_bounds_common_encrypted_type_metadata_on_nested_keys() {
1459 let resources = crate::policy::ResourcePolicy {
1463 max_encryption_metadata_bytes: 64,
1464 ..crate::policy::ResourcePolicy::default()
1465 };
1466 let decryption = crate::policy::DecryptionPolicy {
1467 resources: resources.clone(),
1468 ..crate::policy::DecryptionPolicy::default()
1469 };
1470 let encryption = crate::policy::EncryptionPolicy {
1471 resources,
1472 ..crate::policy::EncryptionPolicy::default()
1473 };
1474 let oversized = "x".repeat(65);
1475
1476 for attribute in ["Type", "MimeType", "Encoding"] {
1477 let xml = format!(
1478 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>"#
1479 );
1480 assert!(matches!(
1481 parse_encrypted_data_with_policy(&xml, &decryption),
1482 Err(XmlEncError::Policy(
1483 crate::policy::PolicyViolation::ResourceLimit {
1484 resource: "encryption metadata bytes",
1485 maximum: 64,
1486 actual: 65,
1487 }
1488 ))
1489 ));
1490
1491 let document = Document::parse(&xml).expect("test template must be XML");
1492 assert!(matches!(
1493 parse_encrypted_data_template_node_with_policy(
1494 document.root_element(),
1495 &encryption,
1496 ),
1497 Err(XmlEncError::Policy(
1498 crate::policy::PolicyViolation::ResourceLimit {
1499 resource: "encryption metadata bytes",
1500 maximum: 64,
1501 actual: 65,
1502 }
1503 ))
1504 ));
1505 }
1506 }
1507
1508 #[test]
1509 fn rejects_non_ascii_base64_before_it_can_cross_the_byte_bound() {
1510 assert!(matches!(
1513 normalize_base64("YWJjéA=="),
1514 Err(XmlEncError::Base64(_))
1515 ));
1516
1517 let mut boundary = "A".repeat(MAX_CIPHER_VALUE_BASE64_LEN - 1);
1518 boundary.push('é');
1519 assert!(matches!(
1520 normalize_base64(&boundary),
1521 Err(XmlEncError::Base64(_))
1522 ));
1523 }
1524}