1#[cfg(any(feature = "xmldsig", feature = "xmlenc"))]
8use std::collections::HashSet;
9#[cfg(feature = "xmldsig")]
10use std::time::SystemTime;
11
12#[cfg(feature = "xmldsig")]
13use crate::xmldsig::{DigestAlgorithm, SignatureAlgorithm, UriTypeSet, XPathHereSemantics};
14#[cfg(feature = "xmlenc")]
15use crate::xmlenc::{
16 DataEncryptionAlgorithm, KeyTransportAlgorithm, KeyWrapAlgorithm, OaepDigestAlgorithm,
17};
18
19pub(crate) mod resource_name {
24 pub const XML_NODES: &str = "XML nodes";
25 pub const XML_DEPTH: &str = "XML element depth";
26 pub const SIGNATURE_REFERENCES: &str = "signature references";
27 pub const REFERENCE_TRANSFORMS: &str = "reference transforms";
28 pub const XML_BASE_COMPONENTS: &str = "XML Base components";
29 pub const XML_BASE_RESOLUTION_BYTES: &str = "XML Base resolution bytes";
30 pub const CANONICALIZED_BYTES: &str = "canonicalized bytes";
31 pub const EXTERNAL_RESOURCE_BYTES: &str = "external resource bytes";
32 pub const AGGREGATE_EXTERNAL_RESOURCE_BYTES: &str = "aggregate external resource bytes";
33 pub const ENCRYPTION_PLAINTEXT_BYTES: &str = "encryption plaintext bytes";
34 #[cfg(feature = "xmlenc")]
35 pub const AGGREGATE_ENCRYPTION_CIPHER_VALUE_BYTES: &str =
36 "aggregate encryption CipherValue bytes";
37 pub const XML_DOCUMENT: &str = "XML document";
38 pub const XML_PARSE_WORK_BYTES: &str = "cumulative XML parse-work bytes";
39 pub const ENCRYPTION_RECIPIENTS: &str = "encryption recipients";
40 pub const ENCRYPTION_METADATA_BYTES: &str = "encryption metadata bytes";
41 pub const KEY_CANDIDATES: &str = "key candidates";
42 pub const KEY_INFO_REFERENCE_DEPTH: &str = "KeyInfoReference depth";
43 pub const BASE64_TRANSFORM_INPUT_BYTES: &str = "Base64 transform input bytes";
44 pub const BASE64_TRANSFORM_OUTPUT_BYTES: &str = "Base64 transform output bytes";
45 pub const XPATH_EXPRESSIONS: &str = "XPath expressions";
46 pub const XPATH_EXPRESSION_BYTES: &str = "XPath expression bytes";
47 pub const XPATH_EXPRESSION_COMPLEXITY: &str = "XPath expression complexity";
48 pub const XPATH_CONTEXT_EVALUATIONS: &str = "XPath context evaluations";
49 pub const XPATH_EVALUATION_WORK: &str = "XPath evaluation work";
50 pub const XPATH_MIRROR_STRING_BYTES: &str = "XPath mirror string bytes";
51 pub const XPATH_STRING_WORK_BYTES: &str = "XPath string-processing work bytes";
52 pub const XPATH_NAMESPACE_BINDINGS: &str = "XPath namespace bindings";
53 pub const XPATH_NAMESPACE_BYTES: &str = "XPath namespace bytes";
54 pub const XPATH_FILTERS: &str = "XPath filters";
55 pub const NODE_SET_FILTER_WORK: &str = "node-set filter work";
56 pub const NODE_SET_ENTRIES: &str = "node-set entries";
57 pub const NODE_SET_OWNED_STRING_BYTES: &str = "node-set owned string bytes";
58 pub const NODE_SET_CUMULATIVE_OWNED_STRING_BYTES: &str =
59 "cumulative node-set owned string bytes";
60}
61
62#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
64#[non_exhaustive]
65pub enum PolicyViolation {
66 #[error("{operation} policy rejects algorithm {algorithm}")]
68 Algorithm {
69 operation: &'static str,
71 algorithm: String,
73 },
74 #[cfg(feature = "xmldsig")]
76 #[error("HMAC output length must be between {minimum} and {maximum} bits: got {actual}")]
77 HmacOutputLength {
78 minimum: usize,
80 maximum: usize,
82 actual: usize,
84 },
85 #[error("{resource} exceeds policy maximum {maximum}: got {actual}")]
87 ResourceLimit {
88 resource: &'static str,
90 maximum: usize,
92 actual: usize,
94 },
95 #[error("{resource} has invalid policy limit {actual}: {requirement}")]
97 InvalidResourceLimit {
98 resource: &'static str,
100 requirement: &'static str,
102 actual: usize,
104 },
105 #[error("key/trust policy rejected the operation: {reason}")]
107 KeyTrust {
108 reason: &'static str,
110 },
111 #[error("XML input policy rejected the operation: {reason}")]
113 XmlInput {
114 reason: &'static str,
116 },
117 #[error("{operation} URI policy rejected the operation: {reason}")]
119 Uri {
120 operation: &'static str,
122 reason: &'static str,
124 },
125 #[error(
127 "{operation} policy requires {key_type} keys between {minimum_bits} and {maximum_bits} bits: got {actual_bits}"
128 )]
129 KeySize {
130 operation: &'static str,
132 key_type: &'static str,
134 minimum_bits: usize,
136 maximum_bits: usize,
138 actual_bits: usize,
140 },
141 #[error("{operation} policy rejects invalid {key_type} key material: {reason}")]
143 InvalidKeyMaterial {
144 operation: &'static str,
146 key_type: &'static str,
148 reason: &'static str,
150 },
151}
152
153#[cfg(feature = "xmldsig")]
155#[derive(Debug, Clone, Copy, PartialEq, Eq)]
156pub struct HmacPolicy {
157 pub minimum_key_bits: usize,
159 pub minimum_output_bits: usize,
161}
162
163#[cfg(feature = "xmldsig")]
164impl Default for HmacPolicy {
165 fn default() -> Self {
166 Self {
167 minimum_key_bits: 128,
168 minimum_output_bits: 128,
169 }
170 }
171}
172
173#[cfg(feature = "xmldsig")]
174impl HmacPolicy {
175 pub(crate) fn validate(self) -> Result<(), PolicyViolation> {
176 if self.minimum_key_bits == 0 || self.minimum_output_bits == 0 {
177 return Err(PolicyViolation::KeyTrust {
178 reason: "HMAC minimum key and output lengths must be nonzero",
179 });
180 }
181 Ok(())
182 }
183
184 pub(crate) fn validate_key_bytes(self, key_bytes: usize) -> Result<(), PolicyViolation> {
185 self.validate_key_bits(key_bytes.saturating_mul(8))
186 }
187
188 pub(crate) fn validate_key_bits(self, key_bits: usize) -> Result<(), PolicyViolation> {
189 if key_bits < self.minimum_key_bits {
190 return Err(PolicyViolation::InvalidKeyMaterial {
191 operation: "HMAC",
192 key_type: "symmetric",
193 reason: "secret is shorter than the configured minimum",
194 });
195 }
196 Ok(())
197 }
198
199 pub(crate) fn validate_output(
200 self,
201 algorithm: SignatureAlgorithm,
202 selected_bits: usize,
203 ) -> Result<(), PolicyViolation> {
204 let maximum = algorithm
205 .hmac_output_bits()
206 .ok_or_else(|| PolicyViolation::Algorithm {
207 operation: "HMAC",
208 algorithm: algorithm.uri().to_owned(),
209 })?;
210 let minimum = self.minimum_output_bits.max(80).max(maximum / 2);
214 if selected_bits < minimum || selected_bits > maximum {
215 return Err(PolicyViolation::HmacOutputLength {
216 minimum,
217 maximum,
218 actual: selected_bits,
219 });
220 }
221 Ok(())
222 }
223}
224
225#[cfg(any(feature = "xmldsig", feature = "xmlenc"))]
227#[derive(Debug, Clone, Copy, PartialEq, Eq)]
228pub struct RsaKeyPolicy {
229 pub minimum_modulus_bits: usize,
231}
232
233#[cfg(feature = "xmldsig")]
235#[derive(Debug, Clone, Copy, PartialEq, Eq)]
236pub struct DsaKeyPolicy {
237 pub minimum_modulus_bits: usize,
239}
240
241#[cfg(feature = "xmldsig")]
242impl Default for DsaKeyPolicy {
243 fn default() -> Self {
244 Self {
245 minimum_modulus_bits: 2048,
246 }
247 }
248}
249
250#[cfg(feature = "xmldsig")]
251impl DsaKeyPolicy {
252 pub fn validate(&self) -> Result<(), PolicyViolation> {
254 validate_modulus_minimum(
255 "minimum DSA modulus bits",
256 self.minimum_modulus_bits,
257 64,
258 "minimum must be a nonzero multiple of 64 bits",
259 crate::hard_limits::DSA_MODULUS_BIT_CEILING,
260 )
261 }
262
263 pub(crate) fn validate_modulus_bits(&self, actual_bits: usize) -> Result<(), PolicyViolation> {
264 self.validate()?;
265 if !(self.minimum_modulus_bits..=crate::hard_limits::DSA_MODULUS_BIT_CEILING)
266 .contains(&actual_bits)
267 {
268 return Err(PolicyViolation::KeySize {
269 operation: "verification",
270 key_type: "DSA",
271 minimum_bits: self.minimum_modulus_bits,
272 maximum_bits: crate::hard_limits::DSA_MODULUS_BIT_CEILING,
273 actual_bits,
274 });
275 }
276 Ok(())
277 }
278}
279
280#[cfg(any(feature = "xmldsig", feature = "xmlenc"))]
281impl Default for RsaKeyPolicy {
282 fn default() -> Self {
283 Self {
284 minimum_modulus_bits: 2048,
285 }
286 }
287}
288
289#[cfg(any(feature = "xmldsig", feature = "xmlenc"))]
290impl RsaKeyPolicy {
291 pub fn validate(&self) -> Result<(), PolicyViolation> {
293 validate_modulus_minimum(
294 "minimum RSA modulus bits",
295 self.minimum_modulus_bits,
296 8,
297 "minimum must be a nonzero whole-byte width",
298 crate::hard_limits::RSA_MODULUS_BIT_CEILING,
299 )
300 }
301
302 pub(crate) fn validate_components(
303 &self,
304 operation: &'static str,
305 modulus: &[u8],
306 exponent: &[u8],
307 ) -> Result<usize, PolicyViolation> {
308 self.validate()?;
309 let modulus = modulus
310 .iter()
311 .position(|byte| *byte != 0)
312 .map(|start| &modulus[start..])
313 .ok_or(PolicyViolation::InvalidKeyMaterial {
314 operation,
315 key_type: "RSA",
316 reason: "modulus is zero",
317 })?;
318 let modulus_bits = modulus
319 .len()
320 .checked_mul(8)
321 .and_then(|width| width.checked_sub(modulus[0].leading_zeros() as usize))
322 .ok_or(PolicyViolation::InvalidKeyMaterial {
323 operation,
324 key_type: "RSA",
325 reason: "modulus width overflows",
326 })?;
327 if !(self.minimum_modulus_bits..=crate::hard_limits::RSA_MODULUS_BIT_CEILING)
328 .contains(&modulus_bits)
329 {
330 return Err(PolicyViolation::KeySize {
331 operation,
332 key_type: "RSA",
333 minimum_bits: self.minimum_modulus_bits,
334 maximum_bits: crate::hard_limits::RSA_MODULUS_BIT_CEILING,
335 actual_bits: modulus_bits,
336 });
337 }
338 if exponent.is_empty() || exponent.len() > 8 {
339 return Err(PolicyViolation::InvalidKeyMaterial {
340 operation,
341 key_type: "RSA",
342 reason: "public exponent has invalid encoding",
343 });
344 }
345 let mut exponent_bytes = [0_u8; 8];
346 exponent_bytes[8 - exponent.len()..].copy_from_slice(exponent);
347 let exponent = u64::from_be_bytes(exponent_bytes);
348 if !(3..=((1_u64 << 33) - 1)).contains(&exponent) || exponent % 2 == 0 {
349 return Err(PolicyViolation::InvalidKeyMaterial {
350 operation,
351 key_type: "RSA",
352 reason: "public exponent is outside the supported odd range",
353 });
354 }
355 Ok(modulus.len())
356 }
357}
358
359#[cfg(any(feature = "xmldsig", feature = "xmlenc"))]
360fn validate_modulus_minimum(
361 resource: &'static str,
362 minimum_bits: usize,
363 alignment_bits: usize,
364 requirement: &'static str,
365 ceiling: usize,
366) -> Result<(), PolicyViolation> {
367 if minimum_bits == 0 || !minimum_bits.is_multiple_of(alignment_bits) {
368 return Err(PolicyViolation::InvalidResourceLimit {
369 resource,
370 requirement,
371 actual: minimum_bits,
372 });
373 }
374 ResourcePolicy::within(resource, minimum_bits, ceiling)
375}
376
377#[derive(Debug, Clone, PartialEq, Eq)]
379pub struct ResourcePolicy {
380 pub max_xml_nodes: usize,
382 pub max_xml_depth: usize,
384 pub max_references: usize,
386 pub max_transforms_per_reference: usize,
388 pub max_xml_base_components: usize,
390 pub max_xml_base_resolution_bytes: usize,
392 pub max_canonicalized_bytes: usize,
394 pub max_external_resource_bytes: usize,
396 pub max_external_resource_total_bytes: usize,
398 pub max_encryption_plaintext_bytes: usize,
400 pub max_xml_document_bytes: usize,
402 pub max_xml_parse_work_bytes: usize,
404 pub max_encryption_recipients: usize,
406 pub max_encryption_metadata_bytes: usize,
408 pub max_key_candidates: usize,
411 pub max_key_info_reference_depth: usize,
413 pub max_base64_transform_input_bytes: usize,
415 pub max_base64_transform_output_bytes: usize,
417 pub max_xpath_expressions: usize,
419 pub max_xpath_expression_bytes: usize,
421 pub max_xpath_expression_complexity: usize,
423 pub max_xpath_context_evaluations: usize,
425 pub max_xpath_evaluation_work: usize,
427 pub max_xpath_mirror_string_bytes: usize,
429 pub max_xpath_string_work_bytes: usize,
431 pub max_xpath_namespace_bindings: usize,
433 pub max_xpath_namespace_bytes: usize,
435 pub max_xpath_filters: usize,
437 pub max_node_set_filter_work: usize,
439 pub max_node_set_entries: usize,
441 pub max_node_set_owned_string_bytes: usize,
443 pub max_node_set_cumulative_owned_string_bytes: usize,
445}
446
447impl Default for ResourcePolicy {
448 fn default() -> Self {
449 Self {
450 max_xml_nodes: crate::hard_limits::XML_DOCUMENT_NODE_CEILING as usize,
451 max_xml_depth: crate::hard_limits::XML_DOCUMENT_DEPTH_CEILING,
452 max_references: crate::hard_limits::SIGNATURE_REFERENCE_CEILING,
453 max_transforms_per_reference: crate::hard_limits::REFERENCE_TRANSFORM_CEILING,
454 max_xml_base_components: crate::hard_limits::XML_BASE_COMPONENT_CEILING,
455 max_xml_base_resolution_bytes: crate::hard_limits::XML_BASE_RESOLUTION_BYTE_CEILING,
456 max_canonicalized_bytes: crate::hard_limits::CANONICALIZED_SIGNATURE_DATA_BYTE_CEILING,
457 max_external_resource_bytes: crate::hard_limits::EXTERNAL_RESOURCE_BYTE_CEILING,
458 max_external_resource_total_bytes:
459 crate::hard_limits::EXTERNAL_RESOURCE_TOTAL_BYTE_CEILING,
460 max_encryption_plaintext_bytes: crate::hard_limits::ENCRYPTION_PLAINTEXT_BYTE_CEILING,
461 max_xml_document_bytes: crate::hard_limits::XML_DOCUMENT_BYTE_CEILING,
462 max_xml_parse_work_bytes: crate::hard_limits::XML_PARSE_WORK_BYTE_CEILING,
463 max_encryption_recipients: crate::hard_limits::ENCRYPTION_RECIPIENT_CEILING,
464 max_encryption_metadata_bytes: crate::hard_limits::ENCRYPTION_METADATA_BYTE_CEILING,
465 max_key_candidates: crate::hard_limits::KEY_CANDIDATE_CEILING,
466 max_key_info_reference_depth: crate::hard_limits::KEY_INFO_REFERENCE_DEPTH_CEILING,
467 max_base64_transform_input_bytes:
468 crate::hard_limits::BASE64_TRANSFORM_INPUT_BYTE_CEILING,
469 max_base64_transform_output_bytes:
470 crate::hard_limits::BASE64_TRANSFORM_OUTPUT_BYTE_CEILING,
471 max_xpath_expressions: crate::hard_limits::XPATH_EXPRESSION_COUNT_CEILING,
472 max_xpath_expression_bytes: crate::hard_limits::XPATH_EXPRESSION_BYTE_CEILING,
473 max_xpath_expression_complexity:
474 crate::hard_limits::XPATH_EXPRESSION_COMPLEXITY_CEILING,
475 max_xpath_context_evaluations: crate::hard_limits::XPATH_CONTEXT_EVALUATION_CEILING,
476 max_xpath_evaluation_work: crate::hard_limits::XPATH_EVALUATION_WORK_CEILING,
477 max_xpath_mirror_string_bytes: crate::hard_limits::XPATH_MIRROR_STRING_BYTE_CEILING,
478 max_xpath_string_work_bytes: crate::hard_limits::XPATH_STRING_WORK_BYTE_CEILING,
479 max_xpath_namespace_bindings: crate::hard_limits::XPATH_NAMESPACE_BINDING_CEILING,
480 max_xpath_namespace_bytes: crate::hard_limits::XPATH_NAMESPACE_BYTE_CEILING,
481 max_xpath_filters: crate::hard_limits::XPATH_FILTER_COUNT_CEILING,
482 max_node_set_filter_work: crate::hard_limits::NODE_SET_FILTER_WORK_CEILING,
483 max_node_set_entries: crate::hard_limits::NODE_SET_ENTRY_CEILING,
484 max_node_set_owned_string_bytes: crate::hard_limits::NODE_SET_OWNED_STRING_BYTE_CEILING,
485 max_node_set_cumulative_owned_string_bytes:
486 crate::hard_limits::NODE_SET_CUMULATIVE_OWNED_STRING_BYTE_CEILING,
487 }
488 }
489}
490
491impl ResourcePolicy {
492 pub fn validate(&self) -> Result<(), PolicyViolation> {
494 Self::within(
495 resource_name::XML_NODES,
496 self.max_xml_nodes,
497 crate::hard_limits::XML_DOCUMENT_NODE_CEILING as usize,
498 )?;
499 Self::within(
500 resource_name::XML_DEPTH,
501 self.max_xml_depth,
502 crate::hard_limits::XML_DOCUMENT_DEPTH_CEILING,
503 )?;
504 Self::within(
505 resource_name::CANONICALIZED_BYTES,
506 self.max_canonicalized_bytes,
507 crate::hard_limits::CANONICALIZED_SIGNATURE_DATA_BYTE_CEILING,
508 )?;
509 Self::within(
510 resource_name::SIGNATURE_REFERENCES,
511 self.max_references,
512 crate::hard_limits::SIGNATURE_REFERENCE_CEILING,
513 )?;
514 Self::within(
515 resource_name::REFERENCE_TRANSFORMS,
516 self.max_transforms_per_reference,
517 crate::hard_limits::REFERENCE_TRANSFORM_CEILING,
518 )?;
519 Self::within(
520 resource_name::XML_BASE_COMPONENTS,
521 self.max_xml_base_components,
522 crate::hard_limits::XML_BASE_COMPONENT_CEILING,
523 )?;
524 Self::within(
525 resource_name::XML_BASE_RESOLUTION_BYTES,
526 self.max_xml_base_resolution_bytes,
527 crate::hard_limits::XML_BASE_RESOLUTION_BYTE_CEILING,
528 )?;
529 Self::within(
530 resource_name::XML_DOCUMENT,
531 self.max_xml_document_bytes,
532 crate::hard_limits::XML_DOCUMENT_BYTE_CEILING,
533 )?;
534 Self::within(
535 resource_name::XML_PARSE_WORK_BYTES,
536 self.max_xml_parse_work_bytes,
537 crate::hard_limits::XML_PARSE_WORK_BYTE_CEILING,
538 )?;
539 Self::within(
540 resource_name::EXTERNAL_RESOURCE_BYTES,
541 self.max_external_resource_bytes,
542 crate::hard_limits::EXTERNAL_RESOURCE_BYTE_CEILING,
543 )?;
544 Self::within(
545 resource_name::AGGREGATE_EXTERNAL_RESOURCE_BYTES,
546 self.max_external_resource_total_bytes,
547 crate::hard_limits::EXTERNAL_RESOURCE_TOTAL_BYTE_CEILING,
548 )?;
549 Self::within(
550 resource_name::ENCRYPTION_PLAINTEXT_BYTES,
551 self.max_encryption_plaintext_bytes,
552 crate::hard_limits::ENCRYPTION_PLAINTEXT_BYTE_CEILING,
553 )?;
554 Self::within(
555 resource_name::ENCRYPTION_RECIPIENTS,
556 self.max_encryption_recipients,
557 crate::hard_limits::ENCRYPTION_RECIPIENT_CEILING,
558 )?;
559 Self::within(
560 resource_name::ENCRYPTION_METADATA_BYTES,
561 self.max_encryption_metadata_bytes,
562 crate::hard_limits::ENCRYPTION_METADATA_BYTE_CEILING,
563 )?;
564 for (resource, selected, ceiling) in [
565 (
566 resource_name::KEY_CANDIDATES,
567 self.max_key_candidates,
568 crate::hard_limits::KEY_CANDIDATE_CEILING,
569 ),
570 (
571 resource_name::KEY_INFO_REFERENCE_DEPTH,
572 self.max_key_info_reference_depth,
573 crate::hard_limits::KEY_INFO_REFERENCE_DEPTH_CEILING,
574 ),
575 (
576 resource_name::BASE64_TRANSFORM_INPUT_BYTES,
577 self.max_base64_transform_input_bytes,
578 crate::hard_limits::BASE64_TRANSFORM_INPUT_BYTE_CEILING,
579 ),
580 (
581 resource_name::BASE64_TRANSFORM_OUTPUT_BYTES,
582 self.max_base64_transform_output_bytes,
583 crate::hard_limits::BASE64_TRANSFORM_OUTPUT_BYTE_CEILING,
584 ),
585 (
586 resource_name::XPATH_EXPRESSIONS,
587 self.max_xpath_expressions,
588 crate::hard_limits::XPATH_EXPRESSION_COUNT_CEILING,
589 ),
590 (
591 resource_name::XPATH_EXPRESSION_BYTES,
592 self.max_xpath_expression_bytes,
593 crate::hard_limits::XPATH_EXPRESSION_BYTE_CEILING,
594 ),
595 (
596 resource_name::XPATH_EXPRESSION_COMPLEXITY,
597 self.max_xpath_expression_complexity,
598 crate::hard_limits::XPATH_EXPRESSION_COMPLEXITY_CEILING,
599 ),
600 (
601 resource_name::XPATH_CONTEXT_EVALUATIONS,
602 self.max_xpath_context_evaluations,
603 crate::hard_limits::XPATH_CONTEXT_EVALUATION_CEILING,
604 ),
605 (
606 resource_name::XPATH_EVALUATION_WORK,
607 self.max_xpath_evaluation_work,
608 crate::hard_limits::XPATH_EVALUATION_WORK_CEILING,
609 ),
610 (
611 resource_name::XPATH_MIRROR_STRING_BYTES,
612 self.max_xpath_mirror_string_bytes,
613 crate::hard_limits::XPATH_MIRROR_STRING_BYTE_CEILING,
614 ),
615 (
616 resource_name::XPATH_STRING_WORK_BYTES,
617 self.max_xpath_string_work_bytes,
618 crate::hard_limits::XPATH_STRING_WORK_BYTE_CEILING,
619 ),
620 (
621 resource_name::XPATH_NAMESPACE_BINDINGS,
622 self.max_xpath_namespace_bindings,
623 crate::hard_limits::XPATH_NAMESPACE_BINDING_CEILING,
624 ),
625 (
626 resource_name::XPATH_NAMESPACE_BYTES,
627 self.max_xpath_namespace_bytes,
628 crate::hard_limits::XPATH_NAMESPACE_BYTE_CEILING,
629 ),
630 (
631 resource_name::XPATH_FILTERS,
632 self.max_xpath_filters,
633 crate::hard_limits::XPATH_FILTER_COUNT_CEILING,
634 ),
635 (
636 resource_name::NODE_SET_FILTER_WORK,
637 self.max_node_set_filter_work,
638 crate::hard_limits::NODE_SET_FILTER_WORK_CEILING,
639 ),
640 (
641 resource_name::NODE_SET_ENTRIES,
642 self.max_node_set_entries,
643 crate::hard_limits::NODE_SET_ENTRY_CEILING,
644 ),
645 (
646 resource_name::NODE_SET_OWNED_STRING_BYTES,
647 self.max_node_set_owned_string_bytes,
648 crate::hard_limits::NODE_SET_OWNED_STRING_BYTE_CEILING,
649 ),
650 (
651 resource_name::NODE_SET_CUMULATIVE_OWNED_STRING_BYTES,
652 self.max_node_set_cumulative_owned_string_bytes,
653 crate::hard_limits::NODE_SET_CUMULATIVE_OWNED_STRING_BYTE_CEILING,
654 ),
655 ] {
656 Self::within(resource, selected, ceiling)?;
657 }
658 Ok(())
659 }
660
661 pub(crate) fn validate_xml_document_len(&self, actual: usize) -> Result<(), PolicyViolation> {
662 if actual > self.max_xml_document_bytes {
663 return Err(PolicyViolation::ResourceLimit {
664 resource: resource_name::XML_DOCUMENT,
665 maximum: self.max_xml_document_bytes,
666 actual,
667 });
668 }
669 Ok(())
670 }
671
672 #[cfg(any(feature = "xmldsig", feature = "xmlenc"))]
673 pub fn validate_key_candidates(&self, actual: usize) -> Result<(), PolicyViolation> {
675 if actual > self.max_key_candidates {
676 return Err(PolicyViolation::ResourceLimit {
677 resource: resource_name::KEY_CANDIDATES,
678 maximum: self.max_key_candidates,
679 actual,
680 });
681 }
682 Ok(())
683 }
684
685 #[cfg(feature = "xmldsig")]
687 pub fn validate_key_info_reference_depth(&self, actual: usize) -> Result<(), PolicyViolation> {
688 if actual > self.max_key_info_reference_depth {
689 return Err(PolicyViolation::ResourceLimit {
690 resource: resource_name::KEY_INFO_REFERENCE_DEPTH,
691 maximum: self.max_key_info_reference_depth,
692 actual,
693 });
694 }
695 Ok(())
696 }
697
698 pub(crate) fn effective_xml_nodes(&self) -> u32 {
699 u32::try_from(self.max_xml_nodes)
700 .unwrap_or(crate::hard_limits::XML_DOCUMENT_NODE_CEILING)
701 .min(crate::hard_limits::XML_DOCUMENT_NODE_CEILING)
702 }
703
704 #[cfg(feature = "xmldsig")]
705 pub(crate) fn effective_canonicalized_bytes(&self) -> usize {
706 self.max_canonicalized_bytes
707 .min(crate::hard_limits::CANONICALIZED_SIGNATURE_DATA_BYTE_CEILING)
708 }
709
710 #[cfg(feature = "xmldsig")]
711 pub(crate) fn effective_xml_base_components(&self) -> usize {
712 self.max_xml_base_components
713 .min(crate::hard_limits::XML_BASE_COMPONENT_CEILING)
714 }
715
716 #[cfg(feature = "xmldsig")]
717 pub(crate) fn effective_xml_base_resolution_bytes(&self) -> usize {
718 self.max_xml_base_resolution_bytes
719 .min(crate::hard_limits::XML_BASE_RESOLUTION_BYTE_CEILING)
720 }
721
722 fn within(
723 resource: &'static str,
724 selected: usize,
725 ceiling: usize,
726 ) -> Result<(), PolicyViolation> {
727 if selected > ceiling {
728 return Err(PolicyViolation::ResourceLimit {
729 resource,
730 maximum: ceiling,
731 actual: selected,
732 });
733 }
734 Ok(())
735 }
736
737 #[cfg(feature = "xmldsig")]
738 fn nonzero_within(
739 resource: &'static str,
740 selected: usize,
741 ceiling: usize,
742 ) -> Result<(), PolicyViolation> {
743 if selected == 0 {
744 return Err(PolicyViolation::InvalidResourceLimit {
745 resource,
746 requirement: "limit must be nonzero",
747 actual: selected,
748 });
749 }
750 Self::within(resource, selected, ceiling)
751 }
752}
753
754#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
756pub struct XmlInputPolicy {
757 pub allow_internal_dtd: bool,
759}
760
761#[cfg(feature = "xmldsig")]
763#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
764pub enum SameDocumentIdSemantics {
765 #[default]
767 Specification,
768 XmlSecBarename,
774 XmlSecVisa3d,
778}
779
780#[cfg(feature = "xmldsig")]
782#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
783pub enum EcdsaSignatureValueEncoding {
784 #[default]
786 XmlDsig,
787 XmlSecAsn1Der,
789}
790
791#[cfg(feature = "xmldsig")]
793#[derive(Debug, Clone, Default, PartialEq, Eq)]
794pub struct TransformPolicy {
795 pub allowed_algorithms: Option<HashSet<String>>,
797 pub xpath_here_semantics: XPathHereSemantics,
799 pub same_document_id_semantics: SameDocumentIdSemantics,
801}
802
803#[cfg(feature = "xmldsig")]
805#[derive(Debug, Clone, Copy, PartialEq, Eq)]
806pub struct UriPolicy {
807 pub references: UriTypeSet,
809 pub retrieval_methods: UriTypeSet,
811 pub key_info_references: UriTypeSet,
813}
814
815#[cfg(feature = "xmldsig")]
816impl Default for UriPolicy {
817 fn default() -> Self {
818 Self {
819 references: UriTypeSet::SAME_DOCUMENT,
820 retrieval_methods: UriTypeSet::SAME_DOCUMENT,
821 key_info_references: UriTypeSet::SAME_DOCUMENT,
822 }
823 }
824}
825
826#[cfg(feature = "xmldsig")]
828#[derive(Debug, Clone, Copy, PartialEq, Eq)]
829pub struct KeySourcePolicy {
830 pub preset_key: bool,
832 pub key_name: bool,
834 pub key_value: bool,
836 pub der_encoded_key_value: bool,
838 pub x509_data: bool,
840 pub key_info_reference: bool,
842}
843
844#[cfg(feature = "xmldsig")]
845impl Default for KeySourcePolicy {
846 fn default() -> Self {
847 Self {
848 preset_key: true,
849 key_name: true,
850 key_value: true,
851 der_encoded_key_value: true,
852 x509_data: true,
853 key_info_reference: true,
854 }
855 }
856}
857
858#[cfg(feature = "xmldsig")]
860#[derive(Debug, Clone, PartialEq, Eq, Hash)]
861#[non_exhaustive]
862pub enum ExtendedKeyPurpose {
863 ServerAuth,
865 ClientAuth,
867 CodeSigning,
869 EmailProtection,
871 TimeStamping,
873 OcspSigning,
875 Other(Vec<u64>),
877}
878
879#[cfg(feature = "xmldsig")]
881#[derive(Debug, Clone, PartialEq, Eq)]
882pub struct KeyTrustPolicy {
883 pub verify_x509_chains: bool,
885 pub max_x509_chain_depth: usize,
887 pub max_x509_candidate_paths: usize,
889 pub allowed_legacy_signature_algorithms: HashSet<SignatureAlgorithm>,
891 pub rsa_keys: RsaKeyPolicy,
893 pub dsa_keys: DsaKeyPolicy,
895 pub allowed_extended_key_usages: HashSet<ExtendedKeyPurpose>,
901 pub check_crls: bool,
904 pub verification_time: Option<SystemTime>,
906}
907
908#[cfg(feature = "xmldsig")]
909impl Default for KeyTrustPolicy {
910 fn default() -> Self {
911 Self {
912 verify_x509_chains: false,
913 max_x509_chain_depth: crate::hard_limits::X509_CHAIN_DEPTH_CEILING,
914 max_x509_candidate_paths: crate::hard_limits::X509_CANDIDATE_PATH_CEILING,
915 allowed_legacy_signature_algorithms: HashSet::new(),
916 rsa_keys: RsaKeyPolicy::default(),
917 dsa_keys: DsaKeyPolicy::default(),
918 allowed_extended_key_usages: HashSet::new(),
919 check_crls: false,
920 verification_time: None,
921 }
922 }
923}
924
925#[cfg(feature = "xmldsig")]
926impl KeyTrustPolicy {
927 pub(crate) fn validate(&self) -> Result<(), PolicyViolation> {
928 if self.check_crls && !self.verify_x509_chains {
929 return Err(PolicyViolation::KeyTrust {
930 reason: "CRL checking requires X.509 chain validation",
931 });
932 }
933 if self
934 .allowed_extended_key_usages
935 .iter()
936 .any(|purpose| match purpose {
937 ExtendedKeyPurpose::Other(arcs) => {
938 arcs.len() < 2 || arcs[0] > 2 || (arcs[0] < 2 && arcs[1] > 39)
939 }
940 _ => false,
941 })
942 {
943 return Err(PolicyViolation::KeyTrust {
944 reason: "custom extended key purposes must contain valid OID arcs",
945 });
946 }
947 self.rsa_keys.validate()?;
948 self.dsa_keys.validate()?;
949 ResourcePolicy::nonzero_within(
950 "X.509 chain depth",
951 self.max_x509_chain_depth,
952 crate::hard_limits::X509_CHAIN_DEPTH_CEILING,
953 )?;
954 ResourcePolicy::nonzero_within(
955 "X.509 candidate paths",
956 self.max_x509_candidate_paths,
957 crate::hard_limits::X509_CANDIDATE_PATH_CEILING,
958 )
959 }
960}
961
962#[cfg(feature = "xmldsig")]
964#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
965pub enum ManifestProcessing {
966 #[default]
968 Ignore,
969 Process,
971}
972
973#[cfg(feature = "xmldsig")]
975#[derive(Debug, Clone, Default)]
976pub struct VerificationPolicy {
977 pub signature_algorithms: Option<HashSet<SignatureAlgorithm>>,
980 pub digest_algorithms: Option<HashSet<DigestAlgorithm>>,
982 pub hmac: HmacPolicy,
984 pub ecdsa_signature_value_encoding: EcdsaSignatureValueEncoding,
986 pub key_trust: KeyTrustPolicy,
988 pub key_sources: KeySourcePolicy,
990 pub uris: UriPolicy,
992 pub transforms: TransformPolicy,
994 pub manifest_processing: ManifestProcessing,
996 pub xml: XmlInputPolicy,
998 pub resources: ResourcePolicy,
1000}
1001
1002#[cfg(feature = "xmldsig")]
1003impl VerificationPolicy {
1004 pub fn validate(&self) -> Result<(), PolicyViolation> {
1006 self.resources.validate()?;
1007 self.key_trust.validate()?;
1008 self.hmac.validate()
1009 }
1010
1011 pub fn check_signature_algorithm(
1013 &self,
1014 algorithm: SignatureAlgorithm,
1015 ) -> Result<(), PolicyViolation> {
1016 if matches!(
1017 algorithm,
1018 SignatureAlgorithm::RsaSha1
1019 | SignatureAlgorithm::DsaSha1
1020 | SignatureAlgorithm::HmacSha1
1021 | SignatureAlgorithm::EcdsaSha1
1022 ) && !self
1023 .key_trust
1024 .allowed_legacy_signature_algorithms
1025 .contains(&algorithm)
1026 {
1027 return Err(PolicyViolation::Algorithm {
1028 operation: "verification",
1029 algorithm: algorithm.uri().to_string(),
1030 });
1031 }
1032 if self
1033 .signature_algorithms
1034 .as_ref()
1035 .is_some_and(|allowed| !allowed.contains(&algorithm))
1036 {
1037 return Err(PolicyViolation::Algorithm {
1038 operation: "verification",
1039 algorithm: algorithm.uri().to_string(),
1040 });
1041 }
1042 Ok(())
1043 }
1044}
1045
1046#[cfg(feature = "xmldsig")]
1048#[derive(Debug, Clone, Default)]
1049pub struct SigningPolicy {
1050 pub signature_algorithms: Option<HashSet<SignatureAlgorithm>>,
1052 pub digest_algorithms: Option<HashSet<DigestAlgorithm>>,
1054 pub hmac: HmacPolicy,
1056 pub ecdsa_signature_value_encoding: EcdsaSignatureValueEncoding,
1058 pub rsa_keys: RsaKeyPolicy,
1060 pub dsa_keys: DsaKeyPolicy,
1062 pub uris: UriPolicy,
1065 pub transforms: TransformPolicy,
1067 pub manifest_processing: ManifestProcessing,
1069 pub xml: XmlInputPolicy,
1071 pub resources: ResourcePolicy,
1073}
1074
1075#[cfg(feature = "xmldsig")]
1076impl SigningPolicy {
1077 pub fn validate(&self) -> Result<(), PolicyViolation> {
1079 self.resources.validate()?;
1080 self.rsa_keys.validate()?;
1081 self.dsa_keys.validate()?;
1082 self.hmac.validate()
1083 }
1084
1085 pub(crate) fn check_signature_algorithm(
1086 &self,
1087 algorithm: SignatureAlgorithm,
1088 ) -> Result<(), PolicyViolation> {
1089 check_signing_algorithm(
1090 self.signature_algorithms.as_ref(),
1091 algorithm,
1092 algorithm.signing_allowed(),
1093 algorithm.uri(),
1094 )
1095 }
1096
1097 pub(crate) fn check_digest_algorithm(
1098 &self,
1099 algorithm: DigestAlgorithm,
1100 ) -> Result<(), PolicyViolation> {
1101 check_signing_algorithm(
1102 self.digest_algorithms.as_ref(),
1103 algorithm,
1104 algorithm.signing_allowed(),
1105 algorithm.uri(),
1106 )
1107 }
1108}
1109
1110#[cfg(feature = "xmldsig")]
1111fn check_signing_algorithm<T: Eq + std::hash::Hash>(
1112 allowlist: Option<&HashSet<T>>,
1113 algorithm: T,
1114 default_allowed: bool,
1115 uri: &str,
1116) -> Result<(), PolicyViolation> {
1117 if allowlist.map_or(default_allowed, |allowed| allowed.contains(&algorithm)) {
1118 Ok(())
1119 } else {
1120 Err(PolicyViolation::Algorithm {
1121 operation: "signing",
1122 algorithm: uri.to_owned(),
1123 })
1124 }
1125}
1126
1127#[cfg(feature = "xmlenc")]
1129#[derive(Debug, Clone, Default)]
1130pub struct EncryptionPolicy {
1131 pub data_algorithms: Option<HashSet<DataEncryptionAlgorithm>>,
1133 pub key_transport_algorithms: Option<HashSet<KeyTransportAlgorithm>>,
1135 pub key_wrap_algorithms: Option<HashSet<KeyWrapAlgorithm>>,
1137 pub oaep_digests: Option<HashSet<OaepDigestAlgorithm>>,
1139 pub rsa_keys: RsaKeyPolicy,
1141 pub xml: XmlInputPolicy,
1143 pub resources: ResourcePolicy,
1145}
1146
1147#[cfg(feature = "xmlenc")]
1148impl EncryptionPolicy {
1149 pub fn validate(&self) -> Result<(), PolicyViolation> {
1151 self.resources.validate()?;
1152 self.rsa_keys.validate()
1153 }
1154}
1155
1156#[cfg(feature = "xmlenc")]
1158#[derive(Debug, Clone, Default)]
1159pub struct DecryptionPolicy {
1160 pub data_algorithms: Option<HashSet<DataEncryptionAlgorithm>>,
1162 pub key_transport_algorithms: Option<HashSet<KeyTransportAlgorithm>>,
1164 pub key_wrap_algorithms: Option<HashSet<KeyWrapAlgorithm>>,
1166 pub oaep_digests: Option<HashSet<OaepDigestAlgorithm>>,
1168 pub xml: XmlInputPolicy,
1170 pub resources: ResourcePolicy,
1172}
1173
1174#[cfg(feature = "xmlenc")]
1175impl DecryptionPolicy {
1176 pub fn validate(&self) -> Result<(), PolicyViolation> {
1178 self.resources.validate()
1179 }
1180}
1181
1182#[cfg(test)]
1183mod tests {
1184 use super::*;
1185
1186 #[test]
1187 fn resource_policy_cannot_exceed_implementation_ceiling() {
1188 let policy = ResourcePolicy {
1189 max_xml_nodes: 100_001,
1190 ..ResourcePolicy::default()
1191 };
1192 assert!(matches!(
1193 policy.validate(),
1194 Err(PolicyViolation::ResourceLimit {
1195 resource: resource_name::XML_NODES,
1196 maximum: 100_000,
1197 actual: 100_001,
1198 })
1199 ));
1200 }
1201
1202 #[test]
1203 fn xml_parse_work_policy_cannot_exceed_implementation_ceiling() {
1204 let policy = ResourcePolicy {
1205 max_xml_parse_work_bytes: crate::hard_limits::XML_PARSE_WORK_BYTE_CEILING
1206 .saturating_add(1),
1207 ..ResourcePolicy::default()
1208 };
1209
1210 assert_eq!(
1211 policy.validate(),
1212 Err(PolicyViolation::ResourceLimit {
1213 resource: resource_name::XML_PARSE_WORK_BYTES,
1214 maximum: crate::hard_limits::XML_PARSE_WORK_BYTE_CEILING,
1215 actual: crate::hard_limits::XML_PARSE_WORK_BYTE_CEILING.saturating_add(1),
1216 })
1217 );
1218 }
1219
1220 #[test]
1221 fn every_resource_policy_field_obeys_its_hard_ceiling() {
1222 type Case = (&'static str, usize, fn(&mut ResourcePolicy) -> &mut usize);
1226 let cases: &[Case] = &[
1227 (
1228 resource_name::XML_NODES,
1229 crate::hard_limits::XML_DOCUMENT_NODE_CEILING as usize,
1230 |p| &mut p.max_xml_nodes,
1231 ),
1232 (
1233 resource_name::XML_DEPTH,
1234 crate::hard_limits::XML_DOCUMENT_DEPTH_CEILING,
1235 |p| &mut p.max_xml_depth,
1236 ),
1237 (
1238 resource_name::SIGNATURE_REFERENCES,
1239 crate::hard_limits::SIGNATURE_REFERENCE_CEILING,
1240 |p| &mut p.max_references,
1241 ),
1242 (
1243 resource_name::REFERENCE_TRANSFORMS,
1244 crate::hard_limits::REFERENCE_TRANSFORM_CEILING,
1245 |p| &mut p.max_transforms_per_reference,
1246 ),
1247 (
1248 resource_name::XML_BASE_COMPONENTS,
1249 crate::hard_limits::XML_BASE_COMPONENT_CEILING,
1250 |p| &mut p.max_xml_base_components,
1251 ),
1252 (
1253 resource_name::XML_BASE_RESOLUTION_BYTES,
1254 crate::hard_limits::XML_BASE_RESOLUTION_BYTE_CEILING,
1255 |p| &mut p.max_xml_base_resolution_bytes,
1256 ),
1257 (
1258 resource_name::CANONICALIZED_BYTES,
1259 crate::hard_limits::CANONICALIZED_SIGNATURE_DATA_BYTE_CEILING,
1260 |p| &mut p.max_canonicalized_bytes,
1261 ),
1262 (
1263 resource_name::EXTERNAL_RESOURCE_BYTES,
1264 crate::hard_limits::EXTERNAL_RESOURCE_BYTE_CEILING,
1265 |p| &mut p.max_external_resource_bytes,
1266 ),
1267 (
1268 resource_name::AGGREGATE_EXTERNAL_RESOURCE_BYTES,
1269 crate::hard_limits::EXTERNAL_RESOURCE_TOTAL_BYTE_CEILING,
1270 |p| &mut p.max_external_resource_total_bytes,
1271 ),
1272 (
1273 resource_name::ENCRYPTION_PLAINTEXT_BYTES,
1274 crate::hard_limits::ENCRYPTION_PLAINTEXT_BYTE_CEILING,
1275 |p| &mut p.max_encryption_plaintext_bytes,
1276 ),
1277 (
1278 resource_name::XML_DOCUMENT,
1279 crate::hard_limits::XML_DOCUMENT_BYTE_CEILING,
1280 |p| &mut p.max_xml_document_bytes,
1281 ),
1282 (
1283 resource_name::XML_PARSE_WORK_BYTES,
1284 crate::hard_limits::XML_PARSE_WORK_BYTE_CEILING,
1285 |p| &mut p.max_xml_parse_work_bytes,
1286 ),
1287 (
1288 resource_name::ENCRYPTION_RECIPIENTS,
1289 crate::hard_limits::ENCRYPTION_RECIPIENT_CEILING,
1290 |p| &mut p.max_encryption_recipients,
1291 ),
1292 (
1293 resource_name::ENCRYPTION_METADATA_BYTES,
1294 crate::hard_limits::ENCRYPTION_METADATA_BYTE_CEILING,
1295 |p| &mut p.max_encryption_metadata_bytes,
1296 ),
1297 (
1298 resource_name::KEY_CANDIDATES,
1299 crate::hard_limits::KEY_CANDIDATE_CEILING,
1300 |p| &mut p.max_key_candidates,
1301 ),
1302 (
1303 resource_name::KEY_INFO_REFERENCE_DEPTH,
1304 crate::hard_limits::KEY_INFO_REFERENCE_DEPTH_CEILING,
1305 |p| &mut p.max_key_info_reference_depth,
1306 ),
1307 (
1308 resource_name::BASE64_TRANSFORM_INPUT_BYTES,
1309 crate::hard_limits::BASE64_TRANSFORM_INPUT_BYTE_CEILING,
1310 |p| &mut p.max_base64_transform_input_bytes,
1311 ),
1312 (
1313 resource_name::BASE64_TRANSFORM_OUTPUT_BYTES,
1314 crate::hard_limits::BASE64_TRANSFORM_OUTPUT_BYTE_CEILING,
1315 |p| &mut p.max_base64_transform_output_bytes,
1316 ),
1317 (
1318 resource_name::XPATH_EXPRESSIONS,
1319 crate::hard_limits::XPATH_EXPRESSION_COUNT_CEILING,
1320 |p| &mut p.max_xpath_expressions,
1321 ),
1322 (
1323 resource_name::XPATH_EXPRESSION_BYTES,
1324 crate::hard_limits::XPATH_EXPRESSION_BYTE_CEILING,
1325 |p| &mut p.max_xpath_expression_bytes,
1326 ),
1327 (
1328 resource_name::XPATH_EXPRESSION_COMPLEXITY,
1329 crate::hard_limits::XPATH_EXPRESSION_COMPLEXITY_CEILING,
1330 |p| &mut p.max_xpath_expression_complexity,
1331 ),
1332 (
1333 resource_name::XPATH_CONTEXT_EVALUATIONS,
1334 crate::hard_limits::XPATH_CONTEXT_EVALUATION_CEILING,
1335 |p| &mut p.max_xpath_context_evaluations,
1336 ),
1337 (
1338 resource_name::XPATH_EVALUATION_WORK,
1339 crate::hard_limits::XPATH_EVALUATION_WORK_CEILING,
1340 |p| &mut p.max_xpath_evaluation_work,
1341 ),
1342 (
1343 resource_name::XPATH_MIRROR_STRING_BYTES,
1344 crate::hard_limits::XPATH_MIRROR_STRING_BYTE_CEILING,
1345 |p| &mut p.max_xpath_mirror_string_bytes,
1346 ),
1347 (
1348 resource_name::XPATH_STRING_WORK_BYTES,
1349 crate::hard_limits::XPATH_STRING_WORK_BYTE_CEILING,
1350 |p| &mut p.max_xpath_string_work_bytes,
1351 ),
1352 (
1353 resource_name::XPATH_NAMESPACE_BINDINGS,
1354 crate::hard_limits::XPATH_NAMESPACE_BINDING_CEILING,
1355 |p| &mut p.max_xpath_namespace_bindings,
1356 ),
1357 (
1358 resource_name::XPATH_NAMESPACE_BYTES,
1359 crate::hard_limits::XPATH_NAMESPACE_BYTE_CEILING,
1360 |p| &mut p.max_xpath_namespace_bytes,
1361 ),
1362 (
1363 resource_name::XPATH_FILTERS,
1364 crate::hard_limits::XPATH_FILTER_COUNT_CEILING,
1365 |p| &mut p.max_xpath_filters,
1366 ),
1367 (
1368 resource_name::NODE_SET_FILTER_WORK,
1369 crate::hard_limits::NODE_SET_FILTER_WORK_CEILING,
1370 |p| &mut p.max_node_set_filter_work,
1371 ),
1372 (
1373 resource_name::NODE_SET_ENTRIES,
1374 crate::hard_limits::NODE_SET_ENTRY_CEILING,
1375 |p| &mut p.max_node_set_entries,
1376 ),
1377 (
1378 resource_name::NODE_SET_OWNED_STRING_BYTES,
1379 crate::hard_limits::NODE_SET_OWNED_STRING_BYTE_CEILING,
1380 |p| &mut p.max_node_set_owned_string_bytes,
1381 ),
1382 (
1383 resource_name::NODE_SET_CUMULATIVE_OWNED_STRING_BYTES,
1384 crate::hard_limits::NODE_SET_CUMULATIVE_OWNED_STRING_BYTE_CEILING,
1385 |p| &mut p.max_node_set_cumulative_owned_string_bytes,
1386 ),
1387 ];
1388
1389 for &(resource, ceiling, field) in cases {
1390 let mut policy = ResourcePolicy::default();
1391 let actual = ceiling.saturating_add(1);
1392 *field(&mut policy) = actual;
1393 assert_eq!(
1394 policy.validate(),
1395 Err(PolicyViolation::ResourceLimit {
1396 resource,
1397 maximum: ceiling,
1398 actual,
1399 }),
1400 "wrong hard-ceiling validation for {resource}",
1401 );
1402 }
1403 }
1404
1405 #[test]
1406 fn resource_policy_accepts_zero_as_a_deny_all_ceiling() {
1407 let policy = ResourcePolicy {
1410 max_xml_nodes: 0,
1411 max_xml_depth: 0,
1412 max_references: 0,
1413 max_transforms_per_reference: 0,
1414 max_xml_base_components: 0,
1415 max_xml_base_resolution_bytes: 0,
1416 max_canonicalized_bytes: 0,
1417 max_external_resource_bytes: 0,
1418 max_external_resource_total_bytes: 0,
1419 max_encryption_plaintext_bytes: 0,
1420 max_xml_document_bytes: 0,
1421 max_xml_parse_work_bytes: 0,
1422 max_encryption_recipients: 0,
1423 max_encryption_metadata_bytes: 0,
1424 max_key_candidates: 0,
1425 max_key_info_reference_depth: 0,
1426 max_base64_transform_input_bytes: 0,
1427 max_base64_transform_output_bytes: 0,
1428 max_xpath_expressions: 0,
1429 max_xpath_expression_bytes: 0,
1430 max_xpath_expression_complexity: 0,
1431 max_xpath_context_evaluations: 0,
1432 max_xpath_evaluation_work: 0,
1433 max_xpath_mirror_string_bytes: 0,
1434 max_xpath_string_work_bytes: 0,
1435 max_xpath_namespace_bindings: 0,
1436 max_xpath_namespace_bytes: 0,
1437 max_xpath_filters: 0,
1438 max_node_set_filter_work: 0,
1439 max_node_set_entries: 0,
1440 max_node_set_owned_string_bytes: 0,
1441 max_node_set_cumulative_owned_string_bytes: 0,
1442 };
1443
1444 assert_eq!(policy.validate(), Ok(()));
1445 }
1446
1447 #[cfg(any(feature = "xmldsig", feature = "xmlenc"))]
1448 #[test]
1449 fn rsa_key_policy_enforces_structure_range_and_explicit_relaxation() {
1450 let secure = RsaKeyPolicy::default();
1451 assert!(matches!(
1452 secure.validate_components("test", &[0x80; 128], &[1, 0, 1]),
1453 Err(PolicyViolation::KeySize {
1454 minimum_bits: 2048,
1455 maximum_bits: 8192,
1456 actual_bits: 1024,
1457 ..
1458 })
1459 ));
1460 let mut short_2048_width = [0_u8; 256];
1461 short_2048_width[0] = 1;
1462 assert!(matches!(
1463 secure.validate_components("test", &short_2048_width, &[1, 0, 1]),
1464 Err(PolicyViolation::KeySize {
1465 minimum_bits: 2048,
1466 actual_bits: 2041,
1467 ..
1468 })
1469 ));
1470 assert!(matches!(
1471 secure.validate_components("test", &[1; 1025], &[1, 0, 1]),
1472 Err(PolicyViolation::KeySize {
1473 actual_bits: 8193,
1474 ..
1475 })
1476 ));
1477 assert!(matches!(
1478 secure.validate_components("test", &[0x80; 256], &[2]),
1479 Err(PolicyViolation::InvalidKeyMaterial { .. })
1480 ));
1481 assert_eq!(
1482 secure.validate_components("test", &[0x80; 256], &[0x80, 0, 0, 1]),
1483 Ok(256),
1484 "normalized RSA components encode the exponent as unsigned bytes"
1485 );
1486
1487 let compatibility = RsaKeyPolicy {
1488 minimum_modulus_bits: 1024,
1489 };
1490 assert_eq!(
1491 compatibility.validate_components("test", &[0x80; 128], &[1, 0, 1]),
1492 Ok(128)
1493 );
1494 assert!(
1495 RsaKeyPolicy {
1496 minimum_modulus_bits: 2047,
1497 }
1498 .validate()
1499 .is_err()
1500 );
1501 }
1502
1503 #[cfg(feature = "xmldsig")]
1504 #[test]
1505 fn mandatory_x509_limits_report_the_nonzero_requirement() {
1506 let policy = KeyTrustPolicy {
1509 max_x509_chain_depth: 0,
1510 ..KeyTrustPolicy::default()
1511 };
1512
1513 let error = policy.validate().expect_err("zero depth must be rejected");
1514 assert!(
1515 error.to_string().contains("must be nonzero"),
1516 "unexpected lower-bound diagnostic: {error}"
1517 );
1518 }
1519
1520 #[cfg(feature = "xmldsig")]
1521 #[test]
1522 fn custom_extended_key_purposes_require_valid_oid_arcs() {
1523 let mut policy = KeyTrustPolicy::default();
1526 policy
1527 .allowed_extended_key_usages
1528 .insert(ExtendedKeyPurpose::Other(vec![1, 40, 7]));
1529
1530 assert!(matches!(
1531 policy.validate(),
1532 Err(PolicyViolation::KeyTrust {
1533 reason: "custom extended key purposes must contain valid OID arcs",
1534 })
1535 ));
1536 }
1537
1538 #[cfg(feature = "xmldsig")]
1539 #[test]
1540 fn crl_checking_requires_x509_chain_validation() {
1541 let policy = KeyTrustPolicy {
1544 check_crls: true,
1545 ..KeyTrustPolicy::default()
1546 };
1547
1548 assert!(matches!(
1549 policy.validate(),
1550 Err(PolicyViolation::KeyTrust {
1551 reason: "CRL checking requires X.509 chain validation"
1552 })
1553 ));
1554 }
1555
1556 #[cfg(feature = "xmldsig")]
1557 #[test]
1558 fn legacy_signature_algorithms_require_independent_policy_opt_ins() {
1559 let legacy = [
1560 SignatureAlgorithm::RsaSha1,
1561 SignatureAlgorithm::DsaSha1,
1562 SignatureAlgorithm::HmacSha1,
1563 SignatureAlgorithm::EcdsaSha1,
1564 ];
1565 let mut policy = VerificationPolicy::default();
1566
1567 for algorithm in legacy {
1568 assert!(matches!(
1569 policy.check_signature_algorithm(algorithm),
1570 Err(PolicyViolation::Algorithm { .. })
1571 ));
1572 policy
1573 .key_trust
1574 .allowed_legacy_signature_algorithms
1575 .insert(algorithm);
1576 assert_eq!(policy.check_signature_algorithm(algorithm), Ok(()));
1577 policy
1578 .key_trust
1579 .allowed_legacy_signature_algorithms
1580 .remove(&algorithm);
1581 }
1582 }
1583
1584 #[cfg(feature = "xmldsig")]
1585 #[test]
1586 fn dsa_key_policy_enforces_configured_minimum_and_hard_ceiling() {
1587 let policy = DsaKeyPolicy::default();
1588
1589 assert!(matches!(
1590 policy.validate_modulus_bits(1024),
1591 Err(PolicyViolation::KeySize {
1592 key_type: "DSA",
1593 minimum_bits: 2048,
1594 maximum_bits: 3072,
1595 actual_bits: 1024,
1596 ..
1597 })
1598 ));
1599 assert_eq!(policy.validate_modulus_bits(2048), Ok(()));
1600 assert!(matches!(
1601 policy.validate_modulus_bits(4096),
1602 Err(PolicyViolation::KeySize {
1603 key_type: "DSA",
1604 minimum_bits: 2048,
1605 maximum_bits: 3072,
1606 actual_bits: 4096,
1607 ..
1608 })
1609 ));
1610 }
1611
1612 #[cfg(feature = "xmldsig")]
1613 #[test]
1614 fn hmac_output_policy_cannot_weaken_the_xmldsig_floor() {
1615 let compatibility = HmacPolicy {
1618 minimum_key_bits: 40,
1619 minimum_output_bits: 40,
1620 };
1621
1622 assert_eq!(
1623 compatibility.validate_output(SignatureAlgorithm::HmacSha1, 80),
1624 Ok(())
1625 );
1626 assert!(matches!(
1627 compatibility.validate_output(SignatureAlgorithm::HmacSha1, 72),
1628 Err(PolicyViolation::HmacOutputLength { minimum: 80, .. })
1629 ));
1630 assert!(matches!(
1631 compatibility.validate_output(SignatureAlgorithm::HmacSha256, 120),
1632 Err(PolicyViolation::HmacOutputLength { minimum: 128, .. })
1633 ));
1634 }
1635
1636 #[cfg(feature = "xmldsig")]
1637 #[test]
1638 fn documented_xmldsig_limits_match_hard_limits() {
1639 let docs = include_str!("../docs/xmldsig.md");
1642 let mib = 1024 * 1024;
1643 assert!(docs.contains(&format!(
1644 "Individual resources are limited to {} MiB",
1645 crate::hard_limits::EXTERNAL_RESOURCE_BYTE_CEILING / mib
1646 )));
1647 assert!(docs.contains(&format!(
1648 "complete map to {} MiB",
1649 crate::hard_limits::EXTERNAL_RESOURCE_TOTAL_BYTE_CEILING / mib
1650 )));
1651 assert!(docs.contains(&format!(
1652 "ceilings are {} components and {} MiB per operation",
1653 crate::hard_limits::XML_BASE_COMPONENT_CEILING,
1654 crate::hard_limits::XML_BASE_RESOLUTION_BYTE_CEILING / mib
1655 )));
1656 }
1657}