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 VERIFICATION_SIGNATURES: &str = "verification signatures";
29 pub const REFERENCE_TRANSFORMS: &str = "reference transforms";
30 pub const XML_BASE_COMPONENTS: &str = "XML Base components";
31 pub const XML_BASE_RESOLUTION_BYTES: &str = "XML Base resolution bytes";
32 pub const CANONICALIZED_BYTES: &str = "canonicalized bytes";
33 pub const EXTERNAL_RESOURCE_BYTES: &str = "external resource bytes";
34 pub const AGGREGATE_EXTERNAL_RESOURCE_BYTES: &str = "aggregate external resource bytes";
35 pub const ENCRYPTION_PLAINTEXT_BYTES: &str = "encryption plaintext bytes";
36 #[cfg(feature = "xmlenc")]
37 pub const AGGREGATE_ENCRYPTION_CIPHER_VALUE_BYTES: &str =
38 "aggregate encryption CipherValue bytes";
39 pub const XML_DOCUMENT: &str = "XML document";
40 pub const XML_PARSE_WORK_BYTES: &str = "cumulative XML parse-work bytes";
41 pub const ENCRYPTION_RECIPIENTS: &str = "encryption recipients";
42 pub const ENCRYPTION_METADATA_BYTES: &str = "encryption metadata bytes";
43 pub const KEY_CANDIDATES: &str = "key candidates";
44 pub const KEY_IMPORT_KDF_WORK: &str = "key import KDF work";
45 pub const KEY_IMPORT_KDF_MEMORY: &str = "key import KDF memory bytes";
46 pub const KEY_INFO_REFERENCE_DEPTH: &str = "KeyInfoReference depth";
47 pub const BASE64_TRANSFORM_INPUT_BYTES: &str = "Base64 transform input bytes";
48 pub const BASE64_TRANSFORM_OUTPUT_BYTES: &str = "Base64 transform output bytes";
49 pub const XPATH_EXPRESSIONS: &str = "XPath expressions";
50 pub const XPATH_EXPRESSION_BYTES: &str = "XPath expression bytes";
51 pub const XPATH_EXPRESSION_COMPLEXITY: &str = "XPath expression complexity";
52 pub const XPATH_CONTEXT_EVALUATIONS: &str = "XPath context evaluations";
53 pub const XPATH_EVALUATION_WORK: &str = "XPath evaluation work";
54 pub const XPATH_MIRROR_STRING_BYTES: &str = "XPath mirror string bytes";
55 pub const XPATH_STRING_WORK_BYTES: &str = "XPath string-processing work bytes";
56 pub const XPATH_NAMESPACE_BINDINGS: &str = "XPath namespace bindings";
57 pub const XPATH_NAMESPACE_BYTES: &str = "XPath namespace bytes";
58 pub const XPATH_FILTERS: &str = "XPath filters";
59 pub const NODE_SET_FILTER_WORK: &str = "node-set filter work";
60 pub const NODE_SET_ENTRIES: &str = "node-set entries";
61 pub const NODE_SET_OWNED_STRING_BYTES: &str = "node-set owned string bytes";
62 pub const NODE_SET_CUMULATIVE_OWNED_STRING_BYTES: &str =
63 "cumulative node-set owned string bytes";
64}
65
66#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
68#[non_exhaustive]
69pub enum PolicyViolation {
70 #[error("{operation} policy rejects algorithm {algorithm}")]
72 Algorithm {
73 operation: &'static str,
75 algorithm: String,
77 },
78 #[cfg(feature = "xmldsig")]
80 #[error("HMAC output length must be between {minimum} and {maximum} bits: got {actual}")]
81 HmacOutputLength {
82 minimum: usize,
84 maximum: usize,
86 actual: usize,
88 },
89 #[error("{resource} exceeds policy maximum {maximum}: got {actual}")]
91 ResourceLimit {
92 resource: &'static str,
94 maximum: usize,
96 actual: usize,
98 },
99 #[error("{resource} exceeds policy maximum {maximum}")]
101 ResourceLimitExceeded {
102 resource: &'static str,
104 maximum: usize,
106 },
107 #[error("key import KDF iterations must be between 1 and {maximum}")]
109 KdfIterationsOutsideLimit {
110 maximum: usize,
112 },
113 #[error("{resource} has invalid policy limit {actual}: {requirement}")]
115 InvalidResourceLimit {
116 resource: &'static str,
118 requirement: &'static str,
120 actual: usize,
122 },
123 #[error("key/trust policy rejected the operation: {reason}")]
125 KeyTrust {
126 reason: &'static str,
128 },
129 #[error("XML input policy rejected the operation: {reason}")]
131 XmlInput {
132 reason: &'static str,
134 },
135 #[error("{operation} URI policy rejected the operation: {reason}")]
137 Uri {
138 operation: &'static str,
140 reason: &'static str,
142 },
143 #[error(
145 "{operation} policy requires {key_type} keys between {minimum_bits} and {maximum_bits} bits: got {actual_bits}"
146 )]
147 KeySize {
148 operation: &'static str,
150 key_type: &'static str,
152 minimum_bits: usize,
154 maximum_bits: usize,
156 actual_bits: usize,
158 },
159 #[error("{operation} policy rejects invalid {key_type} key material: {reason}")]
161 InvalidKeyMaterial {
162 operation: &'static str,
164 key_type: &'static str,
166 reason: &'static str,
168 },
169}
170
171#[cfg(feature = "xmldsig")]
173#[derive(Debug, Clone, Copy, PartialEq, Eq)]
174pub struct HmacPolicy {
175 pub minimum_key_bits: usize,
177 pub minimum_output_bits: usize,
179}
180
181#[cfg(feature = "xmldsig")]
182impl Default for HmacPolicy {
183 fn default() -> Self {
184 Self {
185 minimum_key_bits: 128,
186 minimum_output_bits: 128,
187 }
188 }
189}
190
191#[cfg(feature = "xmldsig")]
192impl HmacPolicy {
193 pub(crate) fn validate(self) -> Result<(), PolicyViolation> {
194 if self.minimum_key_bits == 0 || self.minimum_output_bits == 0 {
195 return Err(PolicyViolation::KeyTrust {
196 reason: "HMAC minimum key and output lengths must be nonzero",
197 });
198 }
199 Ok(())
200 }
201
202 pub(crate) fn validate_key_bytes(self, key_bytes: usize) -> Result<(), PolicyViolation> {
203 self.validate_key_bits(key_bytes.saturating_mul(8))
204 }
205
206 pub(crate) fn validate_key_bits(self, key_bits: usize) -> Result<(), PolicyViolation> {
207 if key_bits < self.minimum_key_bits {
208 return Err(PolicyViolation::InvalidKeyMaterial {
209 operation: "HMAC",
210 key_type: "symmetric",
211 reason: "secret is shorter than the configured minimum",
212 });
213 }
214 Ok(())
215 }
216
217 pub(crate) fn validate_output(
218 self,
219 algorithm: SignatureAlgorithm,
220 selected_bits: usize,
221 ) -> Result<(), PolicyViolation> {
222 let maximum = algorithm
223 .hmac_output_bits()
224 .ok_or_else(|| PolicyViolation::Algorithm {
225 operation: "HMAC",
226 algorithm: algorithm.uri().to_owned(),
227 })?;
228 let minimum = self.minimum_output_bits.max(80).max(maximum / 2);
233 if selected_bits < minimum || selected_bits > maximum {
234 return Err(PolicyViolation::HmacOutputLength {
235 minimum,
236 maximum,
237 actual: selected_bits,
238 });
239 }
240 Ok(())
241 }
242}
243
244#[cfg(any(feature = "xmldsig", feature = "xmlenc"))]
246#[derive(Debug, Clone, Copy, PartialEq, Eq)]
247pub struct RsaKeyPolicy {
248 pub minimum_modulus_bits: usize,
250}
251
252#[cfg(feature = "xmldsig")]
254#[derive(Debug, Clone, Copy, PartialEq, Eq)]
255pub struct DsaKeyPolicy {
256 pub minimum_modulus_bits: usize,
258}
259
260#[cfg(feature = "xmldsig")]
261impl Default for DsaKeyPolicy {
262 fn default() -> Self {
263 Self {
264 minimum_modulus_bits: 2048,
265 }
266 }
267}
268
269#[cfg(feature = "xmldsig")]
270impl DsaKeyPolicy {
271 pub fn validate(&self) -> Result<(), PolicyViolation> {
273 validate_modulus_minimum(
274 "minimum DSA modulus bits",
275 self.minimum_modulus_bits,
276 64,
277 "minimum must be a nonzero multiple of 64 bits",
278 crate::hard_limits::DSA_MODULUS_BIT_CEILING,
279 )
280 }
281
282 pub(crate) fn validate_modulus_bits(&self, actual_bits: usize) -> Result<(), PolicyViolation> {
283 self.validate()?;
284 if !(self.minimum_modulus_bits..=crate::hard_limits::DSA_MODULUS_BIT_CEILING)
285 .contains(&actual_bits)
286 {
287 return Err(PolicyViolation::KeySize {
288 operation: "verification",
289 key_type: "DSA",
290 minimum_bits: self.minimum_modulus_bits,
291 maximum_bits: crate::hard_limits::DSA_MODULUS_BIT_CEILING,
292 actual_bits,
293 });
294 }
295 Ok(())
296 }
297}
298
299#[cfg(any(feature = "xmldsig", feature = "xmlenc"))]
300impl Default for RsaKeyPolicy {
301 fn default() -> Self {
302 Self {
303 minimum_modulus_bits: 2048,
304 }
305 }
306}
307
308#[cfg(any(feature = "xmldsig", feature = "xmlenc"))]
309impl RsaKeyPolicy {
310 pub fn validate(&self) -> Result<(), PolicyViolation> {
312 validate_modulus_minimum(
313 "minimum RSA modulus bits",
314 self.minimum_modulus_bits,
315 8,
316 "minimum must be a nonzero whole-byte width",
317 crate::hard_limits::RSA_MODULUS_BIT_CEILING,
318 )
319 }
320
321 pub(crate) fn validate_components(
322 &self,
323 operation: &'static str,
324 modulus: &[u8],
325 exponent: &[u8],
326 ) -> Result<usize, PolicyViolation> {
327 self.validate()?;
328 let modulus = modulus
329 .iter()
330 .position(|byte| *byte != 0)
331 .map(|start| &modulus[start..])
332 .ok_or(PolicyViolation::InvalidKeyMaterial {
333 operation,
334 key_type: "RSA",
335 reason: "modulus is zero",
336 })?;
337 let modulus_bits = modulus
338 .len()
339 .checked_mul(8)
340 .and_then(|width| width.checked_sub(modulus[0].leading_zeros() as usize))
341 .ok_or(PolicyViolation::InvalidKeyMaterial {
342 operation,
343 key_type: "RSA",
344 reason: "modulus width overflows",
345 })?;
346 let exponent = if exponent.is_empty() || exponent.len() > 8 {
347 None
348 } else {
349 let mut bytes = [0_u8; 8];
350 bytes[8 - exponent.len()..].copy_from_slice(exponent);
351 Some(u64::from_be_bytes(bytes))
352 };
353 self.validate_public_metadata(operation, modulus_bits, exponent)
354 }
355
356 pub(crate) fn validate_public_metadata(
357 &self,
358 operation: &'static str,
359 modulus_bits: usize,
360 exponent: Option<u64>,
361 ) -> Result<usize, PolicyViolation> {
362 self.validate()?;
363 if modulus_bits == 0 {
364 return Err(PolicyViolation::InvalidKeyMaterial {
365 operation,
366 key_type: "RSA",
367 reason: "modulus is zero",
368 });
369 }
370 if !(self.minimum_modulus_bits..=crate::hard_limits::RSA_MODULUS_BIT_CEILING)
371 .contains(&modulus_bits)
372 {
373 return Err(PolicyViolation::KeySize {
374 operation,
375 key_type: "RSA",
376 minimum_bits: self.minimum_modulus_bits,
377 maximum_bits: crate::hard_limits::RSA_MODULUS_BIT_CEILING,
378 actual_bits: modulus_bits,
379 });
380 }
381 let exponent = exponent.ok_or(PolicyViolation::InvalidKeyMaterial {
382 operation,
383 key_type: "RSA",
384 reason: "public exponent has invalid encoding",
385 })?;
386 if !(3..=((1_u64 << 33) - 1)).contains(&exponent) || exponent % 2 == 0 {
387 return Err(PolicyViolation::InvalidKeyMaterial {
388 operation,
389 key_type: "RSA",
390 reason: "public exponent is outside the supported odd range",
391 });
392 }
393 Ok(modulus_bits.div_ceil(8))
394 }
395}
396
397#[cfg(any(feature = "xmldsig", feature = "xmlenc"))]
398fn validate_modulus_minimum(
399 resource: &'static str,
400 minimum_bits: usize,
401 alignment_bits: usize,
402 requirement: &'static str,
403 ceiling: usize,
404) -> Result<(), PolicyViolation> {
405 if minimum_bits == 0 || !minimum_bits.is_multiple_of(alignment_bits) {
406 return Err(PolicyViolation::InvalidResourceLimit {
407 resource,
408 requirement,
409 actual: minimum_bits,
410 });
411 }
412 ResourcePolicy::within(resource, minimum_bits, ceiling)
413}
414
415#[derive(Debug, Clone, PartialEq, Eq)]
417pub struct ResourcePolicy {
418 pub max_xml_nodes: usize,
420 pub max_xml_depth: usize,
422 pub max_xml_namespace_bindings: usize,
425 pub max_references: usize,
427 pub max_signatures: usize,
429 pub max_transforms_per_reference: usize,
431 pub max_xml_base_components: usize,
433 pub max_xml_base_resolution_bytes: usize,
435 pub max_canonicalized_bytes: usize,
437 pub max_external_resource_bytes: usize,
439 pub max_external_resource_total_bytes: usize,
441 pub max_encryption_plaintext_bytes: usize,
443 pub max_xml_document_bytes: usize,
445 pub max_xml_parse_work_bytes: usize,
447 pub max_encryption_recipients: usize,
449 pub max_encryption_metadata_bytes: usize,
451 pub max_key_candidates: usize,
454 pub max_key_import_kdf_work: usize,
457 pub max_key_import_kdf_memory_bytes: usize,
459 pub max_key_info_reference_depth: usize,
461 pub max_base64_transform_input_bytes: usize,
463 pub max_base64_transform_output_bytes: usize,
465 pub max_xpath_expressions: usize,
467 pub max_xpath_expression_bytes: usize,
469 pub max_xpath_expression_complexity: usize,
471 pub max_xpath_context_evaluations: usize,
473 pub max_xpath_evaluation_work: usize,
475 pub max_xpath_mirror_string_bytes: usize,
477 pub max_xpath_string_work_bytes: usize,
479 pub max_xpath_namespace_bindings: usize,
481 pub max_xpath_namespace_bytes: usize,
483 pub max_xpath_filters: usize,
485 pub max_node_set_filter_work: usize,
487 pub max_node_set_entries: usize,
490 pub max_node_set_owned_string_bytes: usize,
492 pub max_node_set_cumulative_owned_string_bytes: usize,
494}
495
496impl Default for ResourcePolicy {
497 fn default() -> Self {
498 Self {
499 max_xml_nodes: crate::hard_limits::XML_DOCUMENT_NODE_CEILING as usize,
500 max_xml_depth: crate::hard_limits::XML_DOCUMENT_DEPTH_CEILING,
501 max_xml_namespace_bindings: crate::hard_limits::XML_NAMESPACE_BINDING_CEILING,
502 max_references: crate::hard_limits::SIGNATURE_REFERENCE_CEILING,
503 max_signatures: crate::hard_limits::VERIFICATION_SIGNATURE_CEILING,
504 max_transforms_per_reference: crate::hard_limits::REFERENCE_TRANSFORM_CEILING,
505 max_xml_base_components: crate::hard_limits::XML_BASE_COMPONENT_CEILING,
506 max_xml_base_resolution_bytes: crate::hard_limits::XML_BASE_RESOLUTION_BYTE_CEILING,
507 max_canonicalized_bytes: crate::hard_limits::CANONICALIZED_SIGNATURE_DATA_BYTE_CEILING,
508 max_external_resource_bytes: crate::hard_limits::EXTERNAL_RESOURCE_BYTE_CEILING,
509 max_external_resource_total_bytes:
510 crate::hard_limits::EXTERNAL_RESOURCE_TOTAL_BYTE_CEILING,
511 max_encryption_plaintext_bytes: crate::hard_limits::ENCRYPTION_PLAINTEXT_BYTE_CEILING,
512 max_xml_document_bytes: crate::hard_limits::XML_DOCUMENT_BYTE_CEILING,
513 max_xml_parse_work_bytes: crate::hard_limits::XML_PARSE_WORK_BYTE_CEILING,
514 max_encryption_recipients: crate::hard_limits::ENCRYPTION_RECIPIENT_CEILING,
515 max_encryption_metadata_bytes: crate::hard_limits::ENCRYPTION_METADATA_BYTE_CEILING,
516 max_key_candidates: crate::hard_limits::KEY_CANDIDATE_CEILING,
517 max_key_import_kdf_work: crate::hard_limits::KEY_IMPORT_KDF_WORK_CEILING as usize,
518 max_key_import_kdf_memory_bytes: crate::hard_limits::KEY_IMPORT_KDF_MEMORY_CEILING,
519 max_key_info_reference_depth: crate::hard_limits::KEY_INFO_REFERENCE_DEPTH_CEILING,
520 max_base64_transform_input_bytes:
521 crate::hard_limits::BASE64_TRANSFORM_INPUT_BYTE_CEILING,
522 max_base64_transform_output_bytes:
523 crate::hard_limits::BASE64_TRANSFORM_OUTPUT_BYTE_CEILING,
524 max_xpath_expressions: crate::hard_limits::XPATH_EXPRESSION_COUNT_CEILING,
525 max_xpath_expression_bytes: crate::hard_limits::XPATH_EXPRESSION_BYTE_CEILING,
526 max_xpath_expression_complexity:
527 crate::hard_limits::XPATH_EXPRESSION_COMPLEXITY_CEILING,
528 max_xpath_context_evaluations: crate::hard_limits::XPATH_CONTEXT_EVALUATION_CEILING,
529 max_xpath_evaluation_work: crate::hard_limits::XPATH_EVALUATION_WORK_CEILING,
530 max_xpath_mirror_string_bytes: crate::hard_limits::XPATH_MIRROR_STRING_BYTE_CEILING,
531 max_xpath_string_work_bytes: crate::hard_limits::XPATH_STRING_WORK_BYTE_CEILING,
532 max_xpath_namespace_bindings: crate::hard_limits::XPATH_NAMESPACE_BINDING_CEILING,
533 max_xpath_namespace_bytes: crate::hard_limits::XPATH_NAMESPACE_BYTE_CEILING,
534 max_xpath_filters: crate::hard_limits::XPATH_FILTER_COUNT_CEILING,
535 max_node_set_filter_work: crate::hard_limits::NODE_SET_FILTER_WORK_CEILING,
536 max_node_set_entries: 262_144,
537 max_node_set_owned_string_bytes: crate::hard_limits::NODE_SET_OWNED_STRING_BYTE_CEILING,
538 max_node_set_cumulative_owned_string_bytes:
539 crate::hard_limits::NODE_SET_CUMULATIVE_OWNED_STRING_BYTE_CEILING,
540 }
541 }
542}
543
544impl ResourcePolicy {
545 pub fn validate(&self) -> Result<(), PolicyViolation> {
547 Self::within(
548 resource_name::XML_NODES,
549 self.max_xml_nodes,
550 crate::hard_limits::XML_DOCUMENT_NODE_CEILING as usize,
551 )?;
552 Self::within(
553 resource_name::XML_DEPTH,
554 self.max_xml_depth,
555 crate::hard_limits::XML_DOCUMENT_DEPTH_CEILING,
556 )?;
557 Self::within(
558 resource_name::XML_NAMESPACE_BINDINGS,
559 self.max_xml_namespace_bindings,
560 crate::hard_limits::XML_NAMESPACE_BINDING_CEILING,
561 )?;
562 Self::within(
563 resource_name::CANONICALIZED_BYTES,
564 self.max_canonicalized_bytes,
565 crate::hard_limits::CANONICALIZED_SIGNATURE_DATA_BYTE_CEILING,
566 )?;
567 Self::within(
568 resource_name::SIGNATURE_REFERENCES,
569 self.max_references,
570 crate::hard_limits::SIGNATURE_REFERENCE_CEILING,
571 )?;
572 Self::within(
573 resource_name::VERIFICATION_SIGNATURES,
574 self.max_signatures,
575 crate::hard_limits::VERIFICATION_SIGNATURE_CEILING,
576 )?;
577 Self::within(
578 resource_name::REFERENCE_TRANSFORMS,
579 self.max_transforms_per_reference,
580 crate::hard_limits::REFERENCE_TRANSFORM_CEILING,
581 )?;
582 Self::within(
583 resource_name::XML_BASE_COMPONENTS,
584 self.max_xml_base_components,
585 crate::hard_limits::XML_BASE_COMPONENT_CEILING,
586 )?;
587 Self::within(
588 resource_name::XML_BASE_RESOLUTION_BYTES,
589 self.max_xml_base_resolution_bytes,
590 crate::hard_limits::XML_BASE_RESOLUTION_BYTE_CEILING,
591 )?;
592 Self::within(
593 resource_name::XML_DOCUMENT,
594 self.max_xml_document_bytes,
595 crate::hard_limits::XML_DOCUMENT_BYTE_CEILING,
596 )?;
597 Self::within(
598 resource_name::XML_PARSE_WORK_BYTES,
599 self.max_xml_parse_work_bytes,
600 crate::hard_limits::XML_PARSE_WORK_BYTE_CEILING,
601 )?;
602 Self::within(
603 resource_name::EXTERNAL_RESOURCE_BYTES,
604 self.max_external_resource_bytes,
605 crate::hard_limits::EXTERNAL_RESOURCE_BYTE_CEILING,
606 )?;
607 Self::within(
608 resource_name::AGGREGATE_EXTERNAL_RESOURCE_BYTES,
609 self.max_external_resource_total_bytes,
610 crate::hard_limits::EXTERNAL_RESOURCE_TOTAL_BYTE_CEILING,
611 )?;
612 Self::within(
613 resource_name::ENCRYPTION_PLAINTEXT_BYTES,
614 self.max_encryption_plaintext_bytes,
615 crate::hard_limits::ENCRYPTION_PLAINTEXT_BYTE_CEILING,
616 )?;
617 Self::within(
618 resource_name::ENCRYPTION_RECIPIENTS,
619 self.max_encryption_recipients,
620 crate::hard_limits::ENCRYPTION_RECIPIENT_CEILING,
621 )?;
622 Self::within(
623 resource_name::ENCRYPTION_METADATA_BYTES,
624 self.max_encryption_metadata_bytes,
625 crate::hard_limits::ENCRYPTION_METADATA_BYTE_CEILING,
626 )?;
627 for (resource, selected, ceiling) in [
628 (
629 resource_name::KEY_CANDIDATES,
630 self.max_key_candidates,
631 crate::hard_limits::KEY_CANDIDATE_CEILING,
632 ),
633 (
634 resource_name::KEY_IMPORT_KDF_WORK,
635 self.max_key_import_kdf_work,
636 crate::hard_limits::KEY_IMPORT_KDF_WORK_CEILING as usize,
637 ),
638 (
639 resource_name::KEY_IMPORT_KDF_MEMORY,
640 self.max_key_import_kdf_memory_bytes,
641 crate::hard_limits::KEY_IMPORT_KDF_MEMORY_CEILING,
642 ),
643 (
644 resource_name::KEY_INFO_REFERENCE_DEPTH,
645 self.max_key_info_reference_depth,
646 crate::hard_limits::KEY_INFO_REFERENCE_DEPTH_CEILING,
647 ),
648 (
649 resource_name::BASE64_TRANSFORM_INPUT_BYTES,
650 self.max_base64_transform_input_bytes,
651 crate::hard_limits::BASE64_TRANSFORM_INPUT_BYTE_CEILING,
652 ),
653 (
654 resource_name::BASE64_TRANSFORM_OUTPUT_BYTES,
655 self.max_base64_transform_output_bytes,
656 crate::hard_limits::BASE64_TRANSFORM_OUTPUT_BYTE_CEILING,
657 ),
658 (
659 resource_name::XPATH_EXPRESSIONS,
660 self.max_xpath_expressions,
661 crate::hard_limits::XPATH_EXPRESSION_COUNT_CEILING,
662 ),
663 (
664 resource_name::XPATH_EXPRESSION_BYTES,
665 self.max_xpath_expression_bytes,
666 crate::hard_limits::XPATH_EXPRESSION_BYTE_CEILING,
667 ),
668 (
669 resource_name::XPATH_EXPRESSION_COMPLEXITY,
670 self.max_xpath_expression_complexity,
671 crate::hard_limits::XPATH_EXPRESSION_COMPLEXITY_CEILING,
672 ),
673 (
674 resource_name::XPATH_CONTEXT_EVALUATIONS,
675 self.max_xpath_context_evaluations,
676 crate::hard_limits::XPATH_CONTEXT_EVALUATION_CEILING,
677 ),
678 (
679 resource_name::XPATH_EVALUATION_WORK,
680 self.max_xpath_evaluation_work,
681 crate::hard_limits::XPATH_EVALUATION_WORK_CEILING,
682 ),
683 (
684 resource_name::XPATH_MIRROR_STRING_BYTES,
685 self.max_xpath_mirror_string_bytes,
686 crate::hard_limits::XPATH_MIRROR_STRING_BYTE_CEILING,
687 ),
688 (
689 resource_name::XPATH_STRING_WORK_BYTES,
690 self.max_xpath_string_work_bytes,
691 crate::hard_limits::XPATH_STRING_WORK_BYTE_CEILING,
692 ),
693 (
694 resource_name::XPATH_NAMESPACE_BINDINGS,
695 self.max_xpath_namespace_bindings,
696 crate::hard_limits::XPATH_NAMESPACE_BINDING_CEILING,
697 ),
698 (
699 resource_name::XPATH_NAMESPACE_BYTES,
700 self.max_xpath_namespace_bytes,
701 crate::hard_limits::XPATH_NAMESPACE_BYTE_CEILING,
702 ),
703 (
704 resource_name::XPATH_FILTERS,
705 self.max_xpath_filters,
706 crate::hard_limits::XPATH_FILTER_COUNT_CEILING,
707 ),
708 (
709 resource_name::NODE_SET_FILTER_WORK,
710 self.max_node_set_filter_work,
711 crate::hard_limits::NODE_SET_FILTER_WORK_CEILING,
712 ),
713 (
714 resource_name::NODE_SET_ENTRIES,
715 self.max_node_set_entries,
716 crate::hard_limits::NODE_SET_ENTRY_CEILING,
717 ),
718 (
719 resource_name::NODE_SET_OWNED_STRING_BYTES,
720 self.max_node_set_owned_string_bytes,
721 crate::hard_limits::NODE_SET_OWNED_STRING_BYTE_CEILING,
722 ),
723 (
724 resource_name::NODE_SET_CUMULATIVE_OWNED_STRING_BYTES,
725 self.max_node_set_cumulative_owned_string_bytes,
726 crate::hard_limits::NODE_SET_CUMULATIVE_OWNED_STRING_BYTE_CEILING,
727 ),
728 ] {
729 Self::within(resource, selected, ceiling)?;
730 }
731 Ok(())
732 }
733
734 pub(crate) fn validate_xml_document_len(&self, actual: usize) -> Result<(), PolicyViolation> {
735 if actual > self.max_xml_document_bytes {
736 return Err(PolicyViolation::ResourceLimit {
737 resource: resource_name::XML_DOCUMENT,
738 maximum: self.max_xml_document_bytes,
739 actual,
740 });
741 }
742 Ok(())
743 }
744
745 #[cfg(any(feature = "xmldsig", feature = "xmlenc"))]
746 pub fn validate_key_candidates(&self, actual: usize) -> Result<(), PolicyViolation> {
748 if actual > self.max_key_candidates {
749 return Err(PolicyViolation::ResourceLimit {
750 resource: resource_name::KEY_CANDIDATES,
751 maximum: self.max_key_candidates,
752 actual,
753 });
754 }
755 Ok(())
756 }
757
758 #[cfg(feature = "xmldsig")]
760 pub fn validate_key_info_reference_depth(&self, actual: usize) -> Result<(), PolicyViolation> {
761 if actual > self.max_key_info_reference_depth {
762 return Err(PolicyViolation::ResourceLimit {
763 resource: resource_name::KEY_INFO_REFERENCE_DEPTH,
764 maximum: self.max_key_info_reference_depth,
765 actual,
766 });
767 }
768 Ok(())
769 }
770
771 pub(crate) fn effective_xml_nodes(&self) -> u32 {
772 u32::try_from(self.max_xml_nodes)
773 .unwrap_or(crate::hard_limits::XML_DOCUMENT_NODE_CEILING)
774 .min(crate::hard_limits::XML_DOCUMENT_NODE_CEILING)
775 }
776
777 #[cfg(feature = "xmldsig")]
778 pub(crate) fn effective_canonicalized_bytes(&self) -> usize {
779 self.max_canonicalized_bytes
780 .min(crate::hard_limits::CANONICALIZED_SIGNATURE_DATA_BYTE_CEILING)
781 }
782
783 #[cfg(feature = "xmldsig")]
784 pub(crate) fn effective_xml_base_components(&self) -> usize {
785 self.max_xml_base_components
786 .min(crate::hard_limits::XML_BASE_COMPONENT_CEILING)
787 }
788
789 #[cfg(feature = "xmldsig")]
790 pub(crate) fn effective_xml_base_resolution_bytes(&self) -> usize {
791 self.max_xml_base_resolution_bytes
792 .min(crate::hard_limits::XML_BASE_RESOLUTION_BYTE_CEILING)
793 }
794
795 fn within(
796 resource: &'static str,
797 selected: usize,
798 ceiling: usize,
799 ) -> Result<(), PolicyViolation> {
800 if selected > ceiling {
801 return Err(PolicyViolation::ResourceLimit {
802 resource,
803 maximum: ceiling,
804 actual: selected,
805 });
806 }
807 Ok(())
808 }
809
810 #[cfg(feature = "xmldsig")]
811 fn nonzero_within(
812 resource: &'static str,
813 selected: usize,
814 ceiling: usize,
815 ) -> Result<(), PolicyViolation> {
816 if selected == 0 {
817 return Err(PolicyViolation::InvalidResourceLimit {
818 resource,
819 requirement: "limit must be nonzero",
820 actual: selected,
821 });
822 }
823 Self::within(resource, selected, ceiling)
824 }
825}
826
827#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
829pub struct XmlInputPolicy {
830 pub allow_internal_dtd: bool,
832}
833
834#[cfg(feature = "xmldsig")]
836#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
837pub enum SameDocumentIdSemantics {
838 #[default]
840 Specification,
841 XmlSecBarename,
847 XmlSecVisa3d,
851}
852
853#[cfg(feature = "xmldsig")]
855#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
856pub enum EcdsaSignatureValueEncoding {
857 #[default]
859 XmlDsig,
860 XmlSecAsn1Der,
862}
863
864#[cfg(feature = "xmldsig")]
866#[derive(Debug, Clone, Default, PartialEq, Eq)]
867pub struct TransformPolicy {
868 pub allowed_algorithms: Option<HashSet<String>>,
870 pub xpath_here_semantics: XPathHereSemantics,
872 pub same_document_id_semantics: SameDocumentIdSemantics,
874}
875
876#[cfg(feature = "xmldsig")]
878#[derive(Debug, Clone, Copy, PartialEq, Eq)]
879pub struct UriPolicy {
880 pub references: UriTypeSet,
882 pub retrieval_methods: UriTypeSet,
884 pub key_info_references: UriTypeSet,
886}
887
888#[cfg(feature = "xmldsig")]
889impl Default for UriPolicy {
890 fn default() -> Self {
891 Self {
892 references: UriTypeSet::SAME_DOCUMENT,
893 retrieval_methods: UriTypeSet::SAME_DOCUMENT,
894 key_info_references: UriTypeSet::SAME_DOCUMENT,
895 }
896 }
897}
898
899#[cfg(feature = "xmldsig")]
901#[derive(Debug, Clone, Copy, PartialEq, Eq)]
902pub struct KeySourcePolicy {
903 pub preset_key: bool,
905 pub key_name: bool,
907 pub key_value: bool,
909 pub der_encoded_key_value: bool,
911 pub x509_data: bool,
913 pub key_info_reference: bool,
915}
916
917#[cfg(feature = "xmldsig")]
918impl Default for KeySourcePolicy {
919 fn default() -> Self {
920 Self {
921 preset_key: true,
922 key_name: true,
923 key_value: true,
924 der_encoded_key_value: true,
925 x509_data: true,
926 key_info_reference: true,
927 }
928 }
929}
930
931#[cfg(feature = "xmldsig")]
933#[derive(Debug, Clone, PartialEq, Eq, Hash)]
934#[non_exhaustive]
935pub enum ExtendedKeyPurpose {
936 ServerAuth,
938 ClientAuth,
940 CodeSigning,
942 EmailProtection,
944 TimeStamping,
946 OcspSigning,
948 Other(Vec<u64>),
950}
951
952#[cfg(feature = "xmldsig")]
954#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
955pub enum VerificationTrustMode {
956 #[default]
958 RequireTrustedKey,
959 CryptographicOnly,
961}
962
963#[cfg(feature = "xmldsig")]
965#[derive(Debug, Clone, PartialEq, Eq)]
966pub struct KeyTrustPolicy {
967 pub mode: VerificationTrustMode,
969 pub verify_x509_chains: bool,
971 pub max_x509_chain_depth: usize,
973 pub max_x509_candidate_paths: usize,
975 pub allowed_legacy_signature_algorithms: HashSet<SignatureAlgorithm>,
977 pub certificate_signature_algorithms: Option<CertificateSignatureAlgorithms>,
982 pub rsa_keys: RsaKeyPolicy,
984 pub dsa_keys: DsaKeyPolicy,
986 pub allowed_extended_key_usages: HashSet<ExtendedKeyPurpose>,
992 pub check_crls: bool,
995 pub verification_time: Option<SystemTime>,
997}
998
999#[cfg(feature = "xmldsig")]
1000impl Default for KeyTrustPolicy {
1001 fn default() -> Self {
1002 Self {
1003 mode: VerificationTrustMode::RequireTrustedKey,
1004 verify_x509_chains: false,
1005 max_x509_chain_depth: crate::hard_limits::X509_CHAIN_DEPTH_CEILING,
1006 max_x509_candidate_paths: crate::hard_limits::X509_CANDIDATE_PATH_CEILING,
1007 allowed_legacy_signature_algorithms: HashSet::new(),
1008 certificate_signature_algorithms: None,
1009 rsa_keys: RsaKeyPolicy::default(),
1010 dsa_keys: DsaKeyPolicy::default(),
1011 allowed_extended_key_usages: HashSet::new(),
1012 check_crls: false,
1013 verification_time: None,
1014 }
1015 }
1016}
1017
1018#[cfg(feature = "xmldsig")]
1019#[derive(Debug, Clone, PartialEq, Eq)]
1020pub enum CertificateSignatureAlgorithms {
1022 AllSupported,
1025 Allowlist(HashSet<crate::provider::X509SignatureAlgorithm>),
1027}
1028
1029#[cfg(feature = "xmldsig")]
1030impl KeyTrustPolicy {
1031 pub(crate) fn check_certificate_signature_algorithm(
1032 algorithm: crate::provider::X509SignatureAlgorithm,
1033 allowed: Option<&CertificateSignatureAlgorithms>,
1034 ) -> Result<(), PolicyViolation> {
1035 let permitted = match allowed {
1039 Some(CertificateSignatureAlgorithms::AllSupported) => true,
1040 Some(CertificateSignatureAlgorithms::Allowlist(allowed)) => {
1041 allowed.contains(&algorithm)
1042 }
1043 None => !matches!(
1044 algorithm,
1045 crate::provider::X509SignatureAlgorithm::PostQuantum(_)
1046 ),
1047 };
1048 if !permitted {
1049 return Err(PolicyViolation::Algorithm {
1050 operation: "certificate/CRL verification",
1051 algorithm: algorithm.oid().to_owned(),
1052 });
1053 }
1054 Ok(())
1055 }
1056
1057 pub(crate) fn validate(&self) -> Result<(), PolicyViolation> {
1058 if self.check_crls && !self.verify_x509_chains {
1059 return Err(PolicyViolation::KeyTrust {
1060 reason: "CRL checking requires X.509 chain validation",
1061 });
1062 }
1063 if self
1064 .allowed_extended_key_usages
1065 .iter()
1066 .any(|purpose| match purpose {
1067 ExtendedKeyPurpose::Other(arcs) => {
1068 arcs.len() < 2 || arcs[0] > 2 || (arcs[0] < 2 && arcs[1] > 39)
1069 }
1070 _ => false,
1071 })
1072 {
1073 return Err(PolicyViolation::KeyTrust {
1074 reason: "custom extended key purposes must contain valid OID arcs",
1075 });
1076 }
1077 self.rsa_keys.validate()?;
1078 self.dsa_keys.validate()?;
1079 ResourcePolicy::nonzero_within(
1080 "X.509 chain depth",
1081 self.max_x509_chain_depth,
1082 crate::hard_limits::X509_CHAIN_DEPTH_CEILING,
1083 )?;
1084 ResourcePolicy::nonzero_within(
1085 "X.509 candidate paths",
1086 self.max_x509_candidate_paths,
1087 crate::hard_limits::X509_CANDIDATE_PATH_CEILING,
1088 )
1089 }
1090}
1091
1092#[cfg(feature = "xmldsig")]
1094#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
1095pub enum ManifestProcessing {
1096 #[default]
1098 Ignore,
1099 Process,
1101}
1102
1103#[cfg(feature = "xmldsig")]
1105#[derive(Debug, Clone, Default)]
1106pub struct VerificationPolicy {
1107 pub signature_algorithms: Option<HashSet<SignatureAlgorithm>>,
1110 pub digest_algorithms: Option<HashSet<DigestAlgorithm>>,
1112 pub hmac: HmacPolicy,
1114 pub ecdsa_signature_value_encoding: EcdsaSignatureValueEncoding,
1116 pub key_trust: KeyTrustPolicy,
1118 pub key_sources: KeySourcePolicy,
1120 pub uris: UriPolicy,
1122 pub transforms: TransformPolicy,
1124 pub manifest_processing: ManifestProcessing,
1126 pub xml: XmlInputPolicy,
1128 pub resources: ResourcePolicy,
1130}
1131
1132#[cfg(feature = "xmldsig")]
1133impl VerificationPolicy {
1134 pub fn validate(&self) -> Result<(), PolicyViolation> {
1136 self.resources.validate()?;
1137 self.key_trust.validate()?;
1138 self.hmac.validate()
1139 }
1140
1141 pub fn check_signature_algorithm(
1143 &self,
1144 algorithm: SignatureAlgorithm,
1145 ) -> Result<(), PolicyViolation> {
1146 if matches!(algorithm, SignatureAlgorithm::PostQuantum(_))
1147 && !self
1148 .signature_algorithms
1149 .as_ref()
1150 .is_some_and(|allowed| allowed.contains(&algorithm))
1151 {
1152 return Err(PolicyViolation::Algorithm {
1153 operation: "verification",
1154 algorithm: algorithm.uri().to_owned(),
1155 });
1156 }
1157 if matches!(
1158 algorithm,
1159 SignatureAlgorithm::RsaSha1
1160 | SignatureAlgorithm::DsaSha1
1161 | SignatureAlgorithm::HmacSha1
1162 | SignatureAlgorithm::EcdsaSha1
1163 ) && !self
1164 .key_trust
1165 .allowed_legacy_signature_algorithms
1166 .contains(&algorithm)
1167 {
1168 return Err(PolicyViolation::Algorithm {
1169 operation: "verification",
1170 algorithm: algorithm.uri().to_string(),
1171 });
1172 }
1173 if self
1174 .signature_algorithms
1175 .as_ref()
1176 .is_some_and(|allowed| !allowed.contains(&algorithm))
1177 {
1178 return Err(PolicyViolation::Algorithm {
1179 operation: "verification",
1180 algorithm: algorithm.uri().to_string(),
1181 });
1182 }
1183 Ok(())
1184 }
1185}
1186
1187#[cfg(feature = "xmldsig")]
1189#[derive(Debug, Clone, Default)]
1190pub struct SigningPolicy {
1191 pub signature_algorithms: Option<HashSet<SignatureAlgorithm>>,
1193 pub digest_algorithms: Option<HashSet<DigestAlgorithm>>,
1195 pub hmac: HmacPolicy,
1197 pub ecdsa_signature_value_encoding: EcdsaSignatureValueEncoding,
1199 pub rsa_keys: RsaKeyPolicy,
1201 pub dsa_keys: DsaKeyPolicy,
1203 pub uris: UriPolicy,
1206 pub transforms: TransformPolicy,
1208 pub manifest_processing: ManifestProcessing,
1210 pub xml: XmlInputPolicy,
1212 pub resources: ResourcePolicy,
1214}
1215
1216#[cfg(feature = "xmldsig")]
1217impl SigningPolicy {
1218 pub fn validate(&self) -> Result<(), PolicyViolation> {
1220 self.resources.validate()?;
1221 self.rsa_keys.validate()?;
1222 self.dsa_keys.validate()?;
1223 self.hmac.validate()
1224 }
1225
1226 pub(crate) fn check_signature_algorithm(
1227 &self,
1228 algorithm: SignatureAlgorithm,
1229 ) -> Result<(), PolicyViolation> {
1230 check_signing_algorithm(
1231 self.signature_algorithms.as_ref(),
1232 algorithm,
1233 algorithm.signing_allowed(),
1234 algorithm.uri(),
1235 )
1236 }
1237
1238 pub(crate) fn check_digest_algorithm(
1239 &self,
1240 algorithm: DigestAlgorithm,
1241 ) -> Result<(), PolicyViolation> {
1242 check_signing_algorithm(
1243 self.digest_algorithms.as_ref(),
1244 algorithm,
1245 algorithm.signing_allowed(),
1246 algorithm.uri(),
1247 )
1248 }
1249}
1250
1251#[cfg(feature = "xmldsig")]
1252fn check_signing_algorithm<T: Eq + std::hash::Hash>(
1253 allowlist: Option<&HashSet<T>>,
1254 algorithm: T,
1255 default_allowed: bool,
1256 uri: &str,
1257) -> Result<(), PolicyViolation> {
1258 if allowlist.map_or(default_allowed, |allowed| allowed.contains(&algorithm)) {
1259 Ok(())
1260 } else {
1261 Err(PolicyViolation::Algorithm {
1262 operation: "signing",
1263 algorithm: uri.to_owned(),
1264 })
1265 }
1266}
1267
1268#[cfg(feature = "xmlenc")]
1270#[derive(Debug, Clone, Default)]
1271pub struct EncryptionPolicy {
1272 pub data_algorithms: Option<HashSet<DataEncryptionAlgorithm>>,
1274 pub key_transport_algorithms: Option<HashSet<KeyTransportAlgorithm>>,
1276 pub key_wrap_algorithms: Option<HashSet<KeyWrapAlgorithm>>,
1278 pub oaep_digests: Option<HashSet<OaepDigestAlgorithm>>,
1280 pub rsa_keys: RsaKeyPolicy,
1282 pub xml: XmlInputPolicy,
1284 pub resources: ResourcePolicy,
1286}
1287
1288#[cfg(feature = "xmlenc")]
1289impl EncryptionPolicy {
1290 pub fn validate(&self) -> Result<(), PolicyViolation> {
1292 self.resources.validate()?;
1293 self.rsa_keys.validate()
1294 }
1295}
1296
1297#[cfg(feature = "xmlenc")]
1299#[derive(Debug, Clone, Default)]
1300pub struct DecryptionPolicy {
1301 pub data_algorithms: Option<HashSet<DataEncryptionAlgorithm>>,
1303 pub key_transport_algorithms: Option<HashSet<KeyTransportAlgorithm>>,
1305 pub key_wrap_algorithms: Option<HashSet<KeyWrapAlgorithm>>,
1307 pub oaep_digests: Option<HashSet<OaepDigestAlgorithm>>,
1309 pub rsa_keys: RsaKeyPolicy,
1312 pub xml: XmlInputPolicy,
1314 pub resources: ResourcePolicy,
1316}
1317
1318#[cfg(feature = "xmlenc")]
1319impl DecryptionPolicy {
1320 pub fn validate(&self) -> Result<(), PolicyViolation> {
1322 self.resources.validate()?;
1323 self.rsa_keys.validate()
1324 }
1325}
1326
1327#[cfg(test)]
1328mod tests {
1329 use super::*;
1330
1331 #[cfg(feature = "xmldsig")]
1332 #[test]
1333 fn certificate_permissions_keep_exact_pss_and_compatibility_distinct() {
1334 use crate::provider::X509SignatureAlgorithm;
1335 use crate::xmldsig::{DigestAlgorithm, PqAlgorithm};
1336 let exact = X509SignatureAlgorithm::RsaPss {
1339 digest: DigestAlgorithm::Sha256,
1340 mgf_digest: DigestAlgorithm::Sha256,
1341 salt_len: 32,
1342 };
1343 let allowed = CertificateSignatureAlgorithms::Allowlist(HashSet::from([exact]));
1344 assert!(
1345 KeyTrustPolicy::check_certificate_signature_algorithm(exact, Some(&allowed)).is_ok()
1346 );
1347 for salt_len in [0, 17, 64, 255] {
1348 let method = X509SignatureAlgorithm::RsaPss {
1349 digest: DigestAlgorithm::Sha256,
1350 mgf_digest: DigestAlgorithm::Sha256,
1351 salt_len,
1352 };
1353 assert!(
1354 KeyTrustPolicy::check_certificate_signature_algorithm(method, Some(&allowed))
1355 .is_err()
1356 );
1357 assert!(
1358 KeyTrustPolicy::check_certificate_signature_algorithm(
1359 method,
1360 Some(&CertificateSignatureAlgorithms::AllSupported)
1361 )
1362 .is_ok()
1363 );
1364 }
1365 let pq = X509SignatureAlgorithm::PostQuantum(PqAlgorithm::MlDsa44);
1366 assert!(KeyTrustPolicy::check_certificate_signature_algorithm(pq, None).is_err());
1367 assert!(
1368 KeyTrustPolicy::check_certificate_signature_algorithm(
1369 pq,
1370 Some(&CertificateSignatureAlgorithms::AllSupported)
1371 )
1372 .is_ok()
1373 );
1374 }
1375
1376 #[test]
1377 fn resource_policy_cannot_exceed_implementation_ceiling() {
1378 let policy = ResourcePolicy {
1379 max_xml_nodes: 100_001,
1380 ..ResourcePolicy::default()
1381 };
1382 assert!(matches!(
1383 policy.validate(),
1384 Err(PolicyViolation::ResourceLimit {
1385 resource: resource_name::XML_NODES,
1386 maximum: 100_000,
1387 actual: 100_001,
1388 })
1389 ));
1390 }
1391
1392 #[test]
1393 fn xml_parse_work_policy_cannot_exceed_implementation_ceiling() {
1394 let policy = ResourcePolicy {
1395 max_xml_parse_work_bytes: crate::hard_limits::XML_PARSE_WORK_BYTE_CEILING
1396 .saturating_add(1),
1397 ..ResourcePolicy::default()
1398 };
1399
1400 assert_eq!(
1401 policy.validate(),
1402 Err(PolicyViolation::ResourceLimit {
1403 resource: resource_name::XML_PARSE_WORK_BYTES,
1404 maximum: crate::hard_limits::XML_PARSE_WORK_BYTE_CEILING,
1405 actual: crate::hard_limits::XML_PARSE_WORK_BYTE_CEILING.saturating_add(1),
1406 })
1407 );
1408 }
1409
1410 #[test]
1411 fn every_resource_policy_field_obeys_its_hard_ceiling() {
1412 type Case = (&'static str, usize, fn(&mut ResourcePolicy) -> &mut usize);
1416 let cases: &[Case] = &[
1417 (
1418 resource_name::XML_NODES,
1419 crate::hard_limits::XML_DOCUMENT_NODE_CEILING as usize,
1420 |p| &mut p.max_xml_nodes,
1421 ),
1422 (
1423 resource_name::XML_DEPTH,
1424 crate::hard_limits::XML_DOCUMENT_DEPTH_CEILING,
1425 |p| &mut p.max_xml_depth,
1426 ),
1427 (
1428 resource_name::XML_NAMESPACE_BINDINGS,
1429 crate::hard_limits::XML_NAMESPACE_BINDING_CEILING,
1430 |p| &mut p.max_xml_namespace_bindings,
1431 ),
1432 (
1433 resource_name::SIGNATURE_REFERENCES,
1434 crate::hard_limits::SIGNATURE_REFERENCE_CEILING,
1435 |p| &mut p.max_references,
1436 ),
1437 (
1438 resource_name::VERIFICATION_SIGNATURES,
1439 crate::hard_limits::VERIFICATION_SIGNATURE_CEILING,
1440 |p| &mut p.max_signatures,
1441 ),
1442 (
1443 resource_name::REFERENCE_TRANSFORMS,
1444 crate::hard_limits::REFERENCE_TRANSFORM_CEILING,
1445 |p| &mut p.max_transforms_per_reference,
1446 ),
1447 (
1448 resource_name::XML_BASE_COMPONENTS,
1449 crate::hard_limits::XML_BASE_COMPONENT_CEILING,
1450 |p| &mut p.max_xml_base_components,
1451 ),
1452 (
1453 resource_name::XML_BASE_RESOLUTION_BYTES,
1454 crate::hard_limits::XML_BASE_RESOLUTION_BYTE_CEILING,
1455 |p| &mut p.max_xml_base_resolution_bytes,
1456 ),
1457 (
1458 resource_name::CANONICALIZED_BYTES,
1459 crate::hard_limits::CANONICALIZED_SIGNATURE_DATA_BYTE_CEILING,
1460 |p| &mut p.max_canonicalized_bytes,
1461 ),
1462 (
1463 resource_name::EXTERNAL_RESOURCE_BYTES,
1464 crate::hard_limits::EXTERNAL_RESOURCE_BYTE_CEILING,
1465 |p| &mut p.max_external_resource_bytes,
1466 ),
1467 (
1468 resource_name::AGGREGATE_EXTERNAL_RESOURCE_BYTES,
1469 crate::hard_limits::EXTERNAL_RESOURCE_TOTAL_BYTE_CEILING,
1470 |p| &mut p.max_external_resource_total_bytes,
1471 ),
1472 (
1473 resource_name::ENCRYPTION_PLAINTEXT_BYTES,
1474 crate::hard_limits::ENCRYPTION_PLAINTEXT_BYTE_CEILING,
1475 |p| &mut p.max_encryption_plaintext_bytes,
1476 ),
1477 (
1478 resource_name::XML_DOCUMENT,
1479 crate::hard_limits::XML_DOCUMENT_BYTE_CEILING,
1480 |p| &mut p.max_xml_document_bytes,
1481 ),
1482 (
1483 resource_name::XML_PARSE_WORK_BYTES,
1484 crate::hard_limits::XML_PARSE_WORK_BYTE_CEILING,
1485 |p| &mut p.max_xml_parse_work_bytes,
1486 ),
1487 (
1488 resource_name::ENCRYPTION_RECIPIENTS,
1489 crate::hard_limits::ENCRYPTION_RECIPIENT_CEILING,
1490 |p| &mut p.max_encryption_recipients,
1491 ),
1492 (
1493 resource_name::ENCRYPTION_METADATA_BYTES,
1494 crate::hard_limits::ENCRYPTION_METADATA_BYTE_CEILING,
1495 |p| &mut p.max_encryption_metadata_bytes,
1496 ),
1497 (
1498 resource_name::KEY_CANDIDATES,
1499 crate::hard_limits::KEY_CANDIDATE_CEILING,
1500 |p| &mut p.max_key_candidates,
1501 ),
1502 (
1503 resource_name::KEY_IMPORT_KDF_WORK,
1504 crate::hard_limits::KEY_IMPORT_KDF_WORK_CEILING as usize,
1505 |p| &mut p.max_key_import_kdf_work,
1506 ),
1507 (
1508 resource_name::KEY_IMPORT_KDF_MEMORY,
1509 crate::hard_limits::KEY_IMPORT_KDF_MEMORY_CEILING,
1510 |p| &mut p.max_key_import_kdf_memory_bytes,
1511 ),
1512 (
1513 resource_name::KEY_INFO_REFERENCE_DEPTH,
1514 crate::hard_limits::KEY_INFO_REFERENCE_DEPTH_CEILING,
1515 |p| &mut p.max_key_info_reference_depth,
1516 ),
1517 (
1518 resource_name::BASE64_TRANSFORM_INPUT_BYTES,
1519 crate::hard_limits::BASE64_TRANSFORM_INPUT_BYTE_CEILING,
1520 |p| &mut p.max_base64_transform_input_bytes,
1521 ),
1522 (
1523 resource_name::BASE64_TRANSFORM_OUTPUT_BYTES,
1524 crate::hard_limits::BASE64_TRANSFORM_OUTPUT_BYTE_CEILING,
1525 |p| &mut p.max_base64_transform_output_bytes,
1526 ),
1527 (
1528 resource_name::XPATH_EXPRESSIONS,
1529 crate::hard_limits::XPATH_EXPRESSION_COUNT_CEILING,
1530 |p| &mut p.max_xpath_expressions,
1531 ),
1532 (
1533 resource_name::XPATH_EXPRESSION_BYTES,
1534 crate::hard_limits::XPATH_EXPRESSION_BYTE_CEILING,
1535 |p| &mut p.max_xpath_expression_bytes,
1536 ),
1537 (
1538 resource_name::XPATH_EXPRESSION_COMPLEXITY,
1539 crate::hard_limits::XPATH_EXPRESSION_COMPLEXITY_CEILING,
1540 |p| &mut p.max_xpath_expression_complexity,
1541 ),
1542 (
1543 resource_name::XPATH_CONTEXT_EVALUATIONS,
1544 crate::hard_limits::XPATH_CONTEXT_EVALUATION_CEILING,
1545 |p| &mut p.max_xpath_context_evaluations,
1546 ),
1547 (
1548 resource_name::XPATH_EVALUATION_WORK,
1549 crate::hard_limits::XPATH_EVALUATION_WORK_CEILING,
1550 |p| &mut p.max_xpath_evaluation_work,
1551 ),
1552 (
1553 resource_name::XPATH_MIRROR_STRING_BYTES,
1554 crate::hard_limits::XPATH_MIRROR_STRING_BYTE_CEILING,
1555 |p| &mut p.max_xpath_mirror_string_bytes,
1556 ),
1557 (
1558 resource_name::XPATH_STRING_WORK_BYTES,
1559 crate::hard_limits::XPATH_STRING_WORK_BYTE_CEILING,
1560 |p| &mut p.max_xpath_string_work_bytes,
1561 ),
1562 (
1563 resource_name::XPATH_NAMESPACE_BINDINGS,
1564 crate::hard_limits::XPATH_NAMESPACE_BINDING_CEILING,
1565 |p| &mut p.max_xpath_namespace_bindings,
1566 ),
1567 (
1568 resource_name::XPATH_NAMESPACE_BYTES,
1569 crate::hard_limits::XPATH_NAMESPACE_BYTE_CEILING,
1570 |p| &mut p.max_xpath_namespace_bytes,
1571 ),
1572 (
1573 resource_name::XPATH_FILTERS,
1574 crate::hard_limits::XPATH_FILTER_COUNT_CEILING,
1575 |p| &mut p.max_xpath_filters,
1576 ),
1577 (
1578 resource_name::NODE_SET_FILTER_WORK,
1579 crate::hard_limits::NODE_SET_FILTER_WORK_CEILING,
1580 |p| &mut p.max_node_set_filter_work,
1581 ),
1582 (
1583 resource_name::NODE_SET_ENTRIES,
1584 crate::hard_limits::NODE_SET_ENTRY_CEILING,
1585 |p| &mut p.max_node_set_entries,
1586 ),
1587 (
1588 resource_name::NODE_SET_OWNED_STRING_BYTES,
1589 crate::hard_limits::NODE_SET_OWNED_STRING_BYTE_CEILING,
1590 |p| &mut p.max_node_set_owned_string_bytes,
1591 ),
1592 (
1593 resource_name::NODE_SET_CUMULATIVE_OWNED_STRING_BYTES,
1594 crate::hard_limits::NODE_SET_CUMULATIVE_OWNED_STRING_BYTE_CEILING,
1595 |p| &mut p.max_node_set_cumulative_owned_string_bytes,
1596 ),
1597 ];
1598
1599 for &(resource, ceiling, field) in cases {
1600 let mut policy = ResourcePolicy::default();
1601 let actual = ceiling.saturating_add(1);
1602 *field(&mut policy) = actual;
1603 assert_eq!(
1604 policy.validate(),
1605 Err(PolicyViolation::ResourceLimit {
1606 resource,
1607 maximum: ceiling,
1608 actual,
1609 }),
1610 "wrong hard-ceiling validation for {resource}",
1611 );
1612 }
1613 }
1614
1615 #[test]
1616 fn resource_policy_accepts_zero_as_a_deny_all_ceiling() {
1617 let policy = ResourcePolicy {
1620 max_xml_nodes: 0,
1621 max_xml_depth: 0,
1622 max_xml_namespace_bindings: 0,
1623 max_references: 0,
1624 max_signatures: 0,
1625 max_transforms_per_reference: 0,
1626 max_xml_base_components: 0,
1627 max_xml_base_resolution_bytes: 0,
1628 max_canonicalized_bytes: 0,
1629 max_external_resource_bytes: 0,
1630 max_external_resource_total_bytes: 0,
1631 max_encryption_plaintext_bytes: 0,
1632 max_xml_document_bytes: 0,
1633 max_xml_parse_work_bytes: 0,
1634 max_encryption_recipients: 0,
1635 max_encryption_metadata_bytes: 0,
1636 max_key_candidates: 0,
1637 max_key_import_kdf_work: 0,
1638 max_key_import_kdf_memory_bytes: 0,
1639 max_key_info_reference_depth: 0,
1640 max_base64_transform_input_bytes: 0,
1641 max_base64_transform_output_bytes: 0,
1642 max_xpath_expressions: 0,
1643 max_xpath_expression_bytes: 0,
1644 max_xpath_expression_complexity: 0,
1645 max_xpath_context_evaluations: 0,
1646 max_xpath_evaluation_work: 0,
1647 max_xpath_mirror_string_bytes: 0,
1648 max_xpath_string_work_bytes: 0,
1649 max_xpath_namespace_bindings: 0,
1650 max_xpath_namespace_bytes: 0,
1651 max_xpath_filters: 0,
1652 max_node_set_filter_work: 0,
1653 max_node_set_entries: 0,
1654 max_node_set_owned_string_bytes: 0,
1655 max_node_set_cumulative_owned_string_bytes: 0,
1656 };
1657
1658 assert_eq!(policy.validate(), Ok(()));
1659 }
1660
1661 #[cfg(any(feature = "xmldsig", feature = "xmlenc"))]
1662 #[test]
1663 fn node_set_policy_has_headroom_above_its_default() {
1664 let mut policy = ResourcePolicy::default();
1665 assert_eq!(policy.max_node_set_entries, 262_144);
1666 assert_eq!(policy.validate(), Ok(()));
1667
1668 policy.max_node_set_entries = 524_288;
1669 assert_eq!(policy.validate(), Ok(()));
1670
1671 policy.max_node_set_entries = 524_289;
1672 assert_eq!(
1673 policy.validate(),
1674 Err(PolicyViolation::ResourceLimit {
1675 resource: resource_name::NODE_SET_ENTRIES,
1676 maximum: 524_288,
1677 actual: 524_289,
1678 })
1679 );
1680 }
1681
1682 #[cfg(any(feature = "xmldsig", feature = "xmlenc"))]
1683 #[test]
1684 fn rsa_key_policy_enforces_structure_range_and_explicit_relaxation() {
1685 let secure = RsaKeyPolicy::default();
1686 assert!(matches!(
1687 secure.validate_components("test", &[0x80; 128], &[1, 0, 1]),
1688 Err(PolicyViolation::KeySize {
1689 minimum_bits: 2048,
1690 maximum_bits: 8192,
1691 actual_bits: 1024,
1692 ..
1693 })
1694 ));
1695 let mut short_2048_width = [0_u8; 256];
1696 short_2048_width[0] = 1;
1697 assert!(matches!(
1698 secure.validate_components("test", &short_2048_width, &[1, 0, 1]),
1699 Err(PolicyViolation::KeySize {
1700 minimum_bits: 2048,
1701 actual_bits: 2041,
1702 ..
1703 })
1704 ));
1705 assert!(matches!(
1706 secure.validate_components("test", &[1; 1025], &[1, 0, 1]),
1707 Err(PolicyViolation::KeySize {
1708 actual_bits: 8193,
1709 ..
1710 })
1711 ));
1712 assert!(matches!(
1713 secure.validate_components("test", &[0x80; 256], &[2]),
1714 Err(PolicyViolation::InvalidKeyMaterial { .. })
1715 ));
1716 assert_eq!(
1717 secure.validate_components("test", &[0x80; 256], &[0x80, 0, 0, 1]),
1718 Ok(256),
1719 "normalized RSA components encode the exponent as unsigned bytes"
1720 );
1721
1722 let compatibility = RsaKeyPolicy {
1723 minimum_modulus_bits: 1024,
1724 };
1725 assert_eq!(
1726 compatibility.validate_components("test", &[0x80; 128], &[1, 0, 1]),
1727 Ok(128)
1728 );
1729 assert!(
1730 RsaKeyPolicy {
1731 minimum_modulus_bits: 2047,
1732 }
1733 .validate()
1734 .is_err()
1735 );
1736 }
1737
1738 #[cfg(feature = "xmldsig")]
1739 #[test]
1740 fn mandatory_x509_limits_report_the_nonzero_requirement() {
1741 let policy = KeyTrustPolicy {
1744 max_x509_chain_depth: 0,
1745 ..KeyTrustPolicy::default()
1746 };
1747
1748 let error = policy.validate().expect_err("zero depth must be rejected");
1749 assert!(
1750 error.to_string().contains("must be nonzero"),
1751 "unexpected lower-bound diagnostic: {error}"
1752 );
1753 }
1754
1755 #[cfg(feature = "xmldsig")]
1756 #[test]
1757 fn custom_extended_key_purposes_require_valid_oid_arcs() {
1758 let mut policy = KeyTrustPolicy::default();
1761 policy
1762 .allowed_extended_key_usages
1763 .insert(ExtendedKeyPurpose::Other(vec![1, 40, 7]));
1764
1765 assert!(matches!(
1766 policy.validate(),
1767 Err(PolicyViolation::KeyTrust {
1768 reason: "custom extended key purposes must contain valid OID arcs",
1769 })
1770 ));
1771 }
1772
1773 #[cfg(feature = "xmldsig")]
1774 #[test]
1775 fn crl_checking_requires_x509_chain_validation() {
1776 let policy = KeyTrustPolicy {
1779 check_crls: true,
1780 ..KeyTrustPolicy::default()
1781 };
1782
1783 assert!(matches!(
1784 policy.validate(),
1785 Err(PolicyViolation::KeyTrust {
1786 reason: "CRL checking requires X.509 chain validation"
1787 })
1788 ));
1789 }
1790
1791 #[cfg(feature = "xmldsig")]
1792 #[test]
1793 fn legacy_signature_algorithms_require_independent_policy_opt_ins() {
1794 let legacy = [
1795 SignatureAlgorithm::RsaSha1,
1796 SignatureAlgorithm::DsaSha1,
1797 SignatureAlgorithm::HmacSha1,
1798 SignatureAlgorithm::EcdsaSha1,
1799 ];
1800 let mut policy = VerificationPolicy::default();
1801
1802 for algorithm in legacy {
1803 assert!(matches!(
1804 policy.check_signature_algorithm(algorithm),
1805 Err(PolicyViolation::Algorithm { .. })
1806 ));
1807 policy
1808 .key_trust
1809 .allowed_legacy_signature_algorithms
1810 .insert(algorithm);
1811 assert_eq!(policy.check_signature_algorithm(algorithm), Ok(()));
1812 policy
1813 .key_trust
1814 .allowed_legacy_signature_algorithms
1815 .remove(&algorithm);
1816 }
1817 }
1818
1819 #[cfg(feature = "xmldsig")]
1820 #[test]
1821 fn dsa_key_policy_enforces_configured_minimum_and_hard_ceiling() {
1822 let policy = DsaKeyPolicy::default();
1823
1824 assert!(matches!(
1825 policy.validate_modulus_bits(1024),
1826 Err(PolicyViolation::KeySize {
1827 key_type: "DSA",
1828 minimum_bits: 2048,
1829 maximum_bits: 3072,
1830 actual_bits: 1024,
1831 ..
1832 })
1833 ));
1834 assert_eq!(policy.validate_modulus_bits(2048), Ok(()));
1835 assert!(matches!(
1836 policy.validate_modulus_bits(4096),
1837 Err(PolicyViolation::KeySize {
1838 key_type: "DSA",
1839 minimum_bits: 2048,
1840 maximum_bits: 3072,
1841 actual_bits: 4096,
1842 ..
1843 })
1844 ));
1845 }
1846
1847 #[cfg(feature = "xmldsig")]
1848 #[test]
1849 fn hmac_output_policy_cannot_weaken_the_xmldsig_floor() {
1850 let compatibility = HmacPolicy {
1853 minimum_key_bits: 40,
1854 minimum_output_bits: 40,
1855 };
1856
1857 assert_eq!(
1858 compatibility.validate_output(SignatureAlgorithm::HmacSha1, 80),
1859 Ok(())
1860 );
1861 assert!(matches!(
1862 compatibility.validate_output(SignatureAlgorithm::HmacSha1, 72),
1863 Err(PolicyViolation::HmacOutputLength { minimum: 80, .. })
1864 ));
1865 assert!(matches!(
1866 compatibility.validate_output(SignatureAlgorithm::HmacSha256, 120),
1867 Err(PolicyViolation::HmacOutputLength { minimum: 128, .. })
1868 ));
1869 }
1870
1871 #[cfg(feature = "xmldsig")]
1872 #[test]
1873 fn documented_xmldsig_limits_match_hard_limits() {
1874 let docs = include_str!("../docs/xmldsig.md");
1877 let mib = 1024 * 1024;
1878 assert!(docs.contains(&format!(
1879 "Individual resources are limited to {} MiB",
1880 crate::hard_limits::EXTERNAL_RESOURCE_BYTE_CEILING / mib
1881 )));
1882 assert!(docs.contains(&format!(
1883 "complete map to {} MiB",
1884 crate::hard_limits::EXTERNAL_RESOURCE_TOTAL_BYTE_CEILING / mib
1885 )));
1886 assert!(docs.contains(&format!(
1887 "ceilings are {} components and {} MiB per operation",
1888 crate::hard_limits::XML_BASE_COMPONENT_CEILING,
1889 crate::hard_limits::XML_BASE_RESOLUTION_BYTE_CEILING / mib
1890 )));
1891 }
1892}