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