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