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