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
1052 .oid()
1053 .map_or_else(|| format!("{algorithm:?}"), str::to_owned),
1054 });
1055 }
1056 Ok(())
1057 }
1058
1059 pub(crate) fn validate(&self) -> Result<(), PolicyViolation> {
1060 if self.check_crls && !self.verify_x509_chains {
1061 return Err(PolicyViolation::KeyTrust {
1062 reason: "CRL checking requires X.509 chain validation",
1063 });
1064 }
1065 if self
1066 .allowed_extended_key_usages
1067 .iter()
1068 .any(|purpose| match purpose {
1069 ExtendedKeyPurpose::Other(arcs) => {
1070 arcs.len() < 2 || arcs[0] > 2 || (arcs[0] < 2 && arcs[1] > 39)
1071 }
1072 _ => false,
1073 })
1074 {
1075 return Err(PolicyViolation::KeyTrust {
1076 reason: "custom extended key purposes must contain valid OID arcs",
1077 });
1078 }
1079 self.rsa_keys.validate()?;
1080 self.dsa_keys.validate()?;
1081 ResourcePolicy::nonzero_within(
1082 "X.509 chain depth",
1083 self.max_x509_chain_depth,
1084 crate::hard_limits::X509_CHAIN_DEPTH_CEILING,
1085 )?;
1086 ResourcePolicy::nonzero_within(
1087 "X.509 candidate paths",
1088 self.max_x509_candidate_paths,
1089 crate::hard_limits::X509_CANDIDATE_PATH_CEILING,
1090 )
1091 }
1092}
1093
1094#[cfg(feature = "xmldsig")]
1096#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
1097pub enum ManifestProcessing {
1098 #[default]
1100 Ignore,
1101 Process,
1103}
1104
1105#[cfg(feature = "xmldsig")]
1107#[derive(Debug, Clone, Default)]
1108pub struct VerificationPolicy {
1109 pub signature_algorithms: Option<HashSet<SignatureAlgorithm>>,
1112 pub digest_algorithms: Option<HashSet<DigestAlgorithm>>,
1114 pub hmac: HmacPolicy,
1116 pub ecdsa_signature_value_encoding: EcdsaSignatureValueEncoding,
1118 pub key_trust: KeyTrustPolicy,
1120 pub key_sources: KeySourcePolicy,
1122 pub uris: UriPolicy,
1124 pub transforms: TransformPolicy,
1126 pub manifest_processing: ManifestProcessing,
1128 pub xml: XmlInputPolicy,
1130 pub resources: ResourcePolicy,
1132}
1133
1134#[cfg(feature = "xmldsig")]
1135impl VerificationPolicy {
1136 pub(crate) fn check_digest_algorithm(
1137 &self,
1138 algorithm: DigestAlgorithm,
1139 ) -> Result<(), PolicyViolation> {
1140 if self
1141 .digest_algorithms
1142 .as_ref()
1143 .map_or(!algorithm.requires_explicit_permission(), |allowed| {
1144 allowed.contains(&algorithm)
1145 })
1146 {
1147 Ok(())
1148 } else {
1149 Err(PolicyViolation::Algorithm {
1150 operation: "verification",
1151 algorithm: algorithm.uri().to_owned(),
1152 })
1153 }
1154 }
1155 pub fn validate(&self) -> Result<(), PolicyViolation> {
1157 self.resources.validate()?;
1158 self.key_trust.validate()?;
1159 self.hmac.validate()
1160 }
1161
1162 pub fn check_signature_algorithm(
1164 &self,
1165 algorithm: SignatureAlgorithm,
1166 ) -> Result<(), PolicyViolation> {
1167 if (matches!(algorithm, SignatureAlgorithm::PostQuantum(_))
1168 || algorithm.requires_explicit_permission())
1169 && !self
1170 .signature_algorithms
1171 .as_ref()
1172 .is_some_and(|allowed| allowed.contains(&algorithm))
1173 {
1174 return Err(PolicyViolation::Algorithm {
1175 operation: "verification",
1176 algorithm: algorithm.uri().to_owned(),
1177 });
1178 }
1179 if matches!(
1180 algorithm,
1181 SignatureAlgorithm::RsaSha1
1182 | SignatureAlgorithm::DsaSha1
1183 | SignatureAlgorithm::HmacSha1
1184 | SignatureAlgorithm::EcdsaSha1
1185 ) && !self
1186 .key_trust
1187 .allowed_legacy_signature_algorithms
1188 .contains(&algorithm)
1189 {
1190 return Err(PolicyViolation::Algorithm {
1191 operation: "verification",
1192 algorithm: algorithm.uri().to_string(),
1193 });
1194 }
1195 if self
1196 .signature_algorithms
1197 .as_ref()
1198 .is_some_and(|allowed| !allowed.contains(&algorithm))
1199 {
1200 return Err(PolicyViolation::Algorithm {
1201 operation: "verification",
1202 algorithm: algorithm.uri().to_string(),
1203 });
1204 }
1205 Ok(())
1206 }
1207}
1208
1209#[cfg(feature = "xmldsig")]
1211#[derive(Debug, Clone, Default)]
1212pub struct SigningPolicy {
1213 pub signature_algorithms: Option<HashSet<SignatureAlgorithm>>,
1215 pub digest_algorithms: Option<HashSet<DigestAlgorithm>>,
1217 pub hmac: HmacPolicy,
1219 pub ecdsa_signature_value_encoding: EcdsaSignatureValueEncoding,
1221 pub rsa_keys: RsaKeyPolicy,
1223 pub dsa_keys: DsaKeyPolicy,
1225 pub uris: UriPolicy,
1228 pub transforms: TransformPolicy,
1230 pub manifest_processing: ManifestProcessing,
1232 pub xml: XmlInputPolicy,
1234 pub resources: ResourcePolicy,
1236}
1237
1238#[cfg(feature = "xmldsig")]
1239impl SigningPolicy {
1240 pub fn validate(&self) -> Result<(), PolicyViolation> {
1242 self.resources.validate()?;
1243 self.rsa_keys.validate()?;
1244 self.dsa_keys.validate()?;
1245 self.hmac.validate()
1246 }
1247
1248 pub(crate) fn check_signature_algorithm(
1249 &self,
1250 algorithm: SignatureAlgorithm,
1251 ) -> Result<(), PolicyViolation> {
1252 check_signing_algorithm(
1253 self.signature_algorithms.as_ref(),
1254 algorithm,
1255 algorithm.signing_allowed(),
1256 algorithm.uri(),
1257 )
1258 }
1259
1260 pub(crate) fn check_digest_algorithm(
1261 &self,
1262 algorithm: DigestAlgorithm,
1263 ) -> Result<(), PolicyViolation> {
1264 check_signing_algorithm(
1265 self.digest_algorithms.as_ref(),
1266 algorithm,
1267 algorithm.signing_allowed(),
1268 algorithm.uri(),
1269 )
1270 }
1271}
1272
1273#[cfg(feature = "xmldsig")]
1274fn check_signing_algorithm<T: Eq + std::hash::Hash>(
1275 allowlist: Option<&HashSet<T>>,
1276 algorithm: T,
1277 default_allowed: bool,
1278 uri: &str,
1279) -> Result<(), PolicyViolation> {
1280 if allowlist.map_or(default_allowed, |allowed| allowed.contains(&algorithm)) {
1281 Ok(())
1282 } else {
1283 Err(PolicyViolation::Algorithm {
1284 operation: "signing",
1285 algorithm: uri.to_owned(),
1286 })
1287 }
1288}
1289
1290#[cfg(feature = "xmlenc")]
1292pub(crate) fn check_content_algorithm(
1293 allowlist: Option<&HashSet<DataEncryptionAlgorithm>>,
1294 algorithm: DataEncryptionAlgorithm,
1295 operation: &'static str,
1296) -> Result<(), PolicyViolation> {
1297 if allowlist.map_or(!algorithm.requires_explicit_permission(), |allowed| {
1298 allowed.contains(&algorithm)
1299 }) {
1300 Ok(())
1301 } else {
1302 Err(PolicyViolation::Algorithm {
1303 operation,
1304 algorithm: algorithm.uri().to_owned(),
1305 })
1306 }
1307}
1308
1309#[cfg(feature = "xmlenc")]
1311#[derive(Debug, Clone, Default)]
1312pub struct EncryptionPolicy {
1313 pub data_algorithms: Option<HashSet<DataEncryptionAlgorithm>>,
1315 pub key_transport_algorithms: Option<HashSet<KeyTransportAlgorithm>>,
1317 pub key_wrap_algorithms: Option<HashSet<KeyWrapAlgorithm>>,
1319 pub oaep_digests: Option<HashSet<OaepDigestAlgorithm>>,
1321 pub rsa_keys: RsaKeyPolicy,
1323 pub xml: XmlInputPolicy,
1325 pub resources: ResourcePolicy,
1327}
1328
1329#[cfg(feature = "xmlenc")]
1330impl EncryptionPolicy {
1331 pub fn validate(&self) -> Result<(), PolicyViolation> {
1333 self.resources.validate()?;
1334 self.rsa_keys.validate()
1335 }
1336}
1337
1338#[cfg(feature = "xmlenc")]
1340#[derive(Debug, Clone, Default)]
1341pub struct DecryptionPolicy {
1342 pub data_algorithms: Option<HashSet<DataEncryptionAlgorithm>>,
1344 pub key_transport_algorithms: Option<HashSet<KeyTransportAlgorithm>>,
1346 pub key_wrap_algorithms: Option<HashSet<KeyWrapAlgorithm>>,
1348 pub oaep_digests: Option<HashSet<OaepDigestAlgorithm>>,
1350 pub rsa_keys: RsaKeyPolicy,
1353 pub xml: XmlInputPolicy,
1355 pub resources: ResourcePolicy,
1357}
1358
1359#[cfg(feature = "xmlenc")]
1360impl DecryptionPolicy {
1361 pub fn validate(&self) -> Result<(), PolicyViolation> {
1363 self.resources.validate()?;
1364 self.rsa_keys.validate()
1365 }
1366}
1367
1368#[cfg(test)]
1369mod tests {
1370 use super::*;
1371
1372 #[cfg(all(feature = "legacy-algorithms", feature = "xmldsig"))]
1373 #[test]
1374 fn legacy_digest_permission_is_explicit_and_operation_local() {
1375 for algorithm in [DigestAlgorithm::Md5, DigestAlgorithm::Ripemd160] {
1378 assert!(
1379 VerificationPolicy::default()
1380 .check_digest_algorithm(algorithm)
1381 .is_err()
1382 );
1383 assert!(
1384 SigningPolicy::default()
1385 .check_digest_algorithm(algorithm)
1386 .is_err()
1387 );
1388 let verification = VerificationPolicy {
1389 digest_algorithms: Some(HashSet::from([algorithm])),
1390 ..VerificationPolicy::default()
1391 };
1392 let signing = SigningPolicy {
1393 digest_algorithms: Some(HashSet::from([algorithm])),
1394 ..SigningPolicy::default()
1395 };
1396 assert!(verification.check_digest_algorithm(algorithm).is_ok());
1397 assert!(signing.check_digest_algorithm(algorithm).is_ok());
1398 assert!(
1399 verification
1400 .check_digest_algorithm(DigestAlgorithm::Sha256)
1401 .is_err()
1402 );
1403 assert!(
1404 signing
1405 .check_digest_algorithm(DigestAlgorithm::Sha256)
1406 .is_err()
1407 );
1408 }
1409 }
1410
1411 #[cfg(feature = "xmldsig")]
1412 #[test]
1413 fn certificate_permissions_keep_exact_pss_and_compatibility_distinct() {
1414 use crate::provider::X509SignatureAlgorithm;
1415 use crate::xmldsig::{DigestAlgorithm, PqAlgorithm};
1416 let exact = X509SignatureAlgorithm::RsaPss {
1419 digest: DigestAlgorithm::Sha256,
1420 mgf_digest: DigestAlgorithm::Sha256,
1421 salt_len: 32,
1422 };
1423 let allowed = CertificateSignatureAlgorithms::Allowlist(HashSet::from([exact]));
1424 assert!(
1425 KeyTrustPolicy::check_certificate_signature_algorithm(exact, Some(&allowed)).is_ok()
1426 );
1427 for salt_len in [0, 17, 64, 255] {
1428 let method = X509SignatureAlgorithm::RsaPss {
1429 digest: DigestAlgorithm::Sha256,
1430 mgf_digest: DigestAlgorithm::Sha256,
1431 salt_len,
1432 };
1433 assert!(
1434 KeyTrustPolicy::check_certificate_signature_algorithm(method, Some(&allowed))
1435 .is_err()
1436 );
1437 assert!(
1438 KeyTrustPolicy::check_certificate_signature_algorithm(
1439 method,
1440 Some(&CertificateSignatureAlgorithms::AllSupported)
1441 )
1442 .is_ok()
1443 );
1444 }
1445 let pq = X509SignatureAlgorithm::PostQuantum(PqAlgorithm::MlDsa44);
1446 assert!(KeyTrustPolicy::check_certificate_signature_algorithm(pq, None).is_err());
1447 assert!(
1448 KeyTrustPolicy::check_certificate_signature_algorithm(
1449 pq,
1450 Some(&CertificateSignatureAlgorithms::AllSupported)
1451 )
1452 .is_ok()
1453 );
1454 }
1455
1456 #[test]
1457 fn resource_policy_cannot_exceed_implementation_ceiling() {
1458 let policy = ResourcePolicy {
1459 max_xml_nodes: 100_001,
1460 ..ResourcePolicy::default()
1461 };
1462 assert!(matches!(
1463 policy.validate(),
1464 Err(PolicyViolation::ResourceLimit {
1465 resource: resource_name::XML_NODES,
1466 maximum: 100_000,
1467 actual: 100_001,
1468 })
1469 ));
1470 }
1471
1472 #[test]
1473 fn xml_parse_work_policy_cannot_exceed_implementation_ceiling() {
1474 let policy = ResourcePolicy {
1475 max_xml_parse_work_bytes: crate::hard_limits::XML_PARSE_WORK_BYTE_CEILING
1476 .saturating_add(1),
1477 ..ResourcePolicy::default()
1478 };
1479
1480 assert_eq!(
1481 policy.validate(),
1482 Err(PolicyViolation::ResourceLimit {
1483 resource: resource_name::XML_PARSE_WORK_BYTES,
1484 maximum: crate::hard_limits::XML_PARSE_WORK_BYTE_CEILING,
1485 actual: crate::hard_limits::XML_PARSE_WORK_BYTE_CEILING.saturating_add(1),
1486 })
1487 );
1488 }
1489
1490 #[test]
1491 fn every_resource_policy_field_obeys_its_hard_ceiling() {
1492 type Case = (&'static str, usize, fn(&mut ResourcePolicy) -> &mut usize);
1496 let cases: &[Case] = &[
1497 (
1498 resource_name::XML_NODES,
1499 crate::hard_limits::XML_DOCUMENT_NODE_CEILING as usize,
1500 |p| &mut p.max_xml_nodes,
1501 ),
1502 (
1503 resource_name::XML_DEPTH,
1504 crate::hard_limits::XML_DOCUMENT_DEPTH_CEILING,
1505 |p| &mut p.max_xml_depth,
1506 ),
1507 (
1508 resource_name::XML_NAMESPACE_BINDINGS,
1509 crate::hard_limits::XML_NAMESPACE_BINDING_CEILING,
1510 |p| &mut p.max_xml_namespace_bindings,
1511 ),
1512 (
1513 resource_name::SIGNATURE_REFERENCES,
1514 crate::hard_limits::SIGNATURE_REFERENCE_CEILING,
1515 |p| &mut p.max_references,
1516 ),
1517 (
1518 resource_name::VERIFICATION_SIGNATURES,
1519 crate::hard_limits::VERIFICATION_SIGNATURE_CEILING,
1520 |p| &mut p.max_signatures,
1521 ),
1522 (
1523 resource_name::REFERENCE_TRANSFORMS,
1524 crate::hard_limits::REFERENCE_TRANSFORM_CEILING,
1525 |p| &mut p.max_transforms_per_reference,
1526 ),
1527 (
1528 resource_name::XML_BASE_COMPONENTS,
1529 crate::hard_limits::XML_BASE_COMPONENT_CEILING,
1530 |p| &mut p.max_xml_base_components,
1531 ),
1532 (
1533 resource_name::XML_BASE_RESOLUTION_BYTES,
1534 crate::hard_limits::XML_BASE_RESOLUTION_BYTE_CEILING,
1535 |p| &mut p.max_xml_base_resolution_bytes,
1536 ),
1537 (
1538 resource_name::CANONICALIZED_BYTES,
1539 crate::hard_limits::CANONICALIZED_SIGNATURE_DATA_BYTE_CEILING,
1540 |p| &mut p.max_canonicalized_bytes,
1541 ),
1542 (
1543 resource_name::EXTERNAL_RESOURCE_BYTES,
1544 crate::hard_limits::EXTERNAL_RESOURCE_BYTE_CEILING,
1545 |p| &mut p.max_external_resource_bytes,
1546 ),
1547 (
1548 resource_name::AGGREGATE_EXTERNAL_RESOURCE_BYTES,
1549 crate::hard_limits::EXTERNAL_RESOURCE_TOTAL_BYTE_CEILING,
1550 |p| &mut p.max_external_resource_total_bytes,
1551 ),
1552 (
1553 resource_name::ENCRYPTION_PLAINTEXT_BYTES,
1554 crate::hard_limits::ENCRYPTION_PLAINTEXT_BYTE_CEILING,
1555 |p| &mut p.max_encryption_plaintext_bytes,
1556 ),
1557 (
1558 resource_name::XML_DOCUMENT,
1559 crate::hard_limits::XML_DOCUMENT_BYTE_CEILING,
1560 |p| &mut p.max_xml_document_bytes,
1561 ),
1562 (
1563 resource_name::XML_PARSE_WORK_BYTES,
1564 crate::hard_limits::XML_PARSE_WORK_BYTE_CEILING,
1565 |p| &mut p.max_xml_parse_work_bytes,
1566 ),
1567 (
1568 resource_name::ENCRYPTION_RECIPIENTS,
1569 crate::hard_limits::ENCRYPTION_RECIPIENT_CEILING,
1570 |p| &mut p.max_encryption_recipients,
1571 ),
1572 (
1573 resource_name::ENCRYPTION_METADATA_BYTES,
1574 crate::hard_limits::ENCRYPTION_METADATA_BYTE_CEILING,
1575 |p| &mut p.max_encryption_metadata_bytes,
1576 ),
1577 (
1578 resource_name::KEY_CANDIDATES,
1579 crate::hard_limits::KEY_CANDIDATE_CEILING,
1580 |p| &mut p.max_key_candidates,
1581 ),
1582 (
1583 resource_name::KEY_IMPORT_KDF_WORK,
1584 crate::hard_limits::KEY_IMPORT_KDF_WORK_CEILING as usize,
1585 |p| &mut p.max_key_import_kdf_work,
1586 ),
1587 (
1588 resource_name::KEY_IMPORT_KDF_MEMORY,
1589 crate::hard_limits::KEY_IMPORT_KDF_MEMORY_CEILING,
1590 |p| &mut p.max_key_import_kdf_memory_bytes,
1591 ),
1592 (
1593 resource_name::KEY_INFO_REFERENCE_DEPTH,
1594 crate::hard_limits::KEY_INFO_REFERENCE_DEPTH_CEILING,
1595 |p| &mut p.max_key_info_reference_depth,
1596 ),
1597 (
1598 resource_name::BASE64_TRANSFORM_INPUT_BYTES,
1599 crate::hard_limits::BASE64_TRANSFORM_INPUT_BYTE_CEILING,
1600 |p| &mut p.max_base64_transform_input_bytes,
1601 ),
1602 (
1603 resource_name::BASE64_TRANSFORM_OUTPUT_BYTES,
1604 crate::hard_limits::BASE64_TRANSFORM_OUTPUT_BYTE_CEILING,
1605 |p| &mut p.max_base64_transform_output_bytes,
1606 ),
1607 (
1608 resource_name::XPATH_EXPRESSIONS,
1609 crate::hard_limits::XPATH_EXPRESSION_COUNT_CEILING,
1610 |p| &mut p.max_xpath_expressions,
1611 ),
1612 (
1613 resource_name::XPATH_EXPRESSION_BYTES,
1614 crate::hard_limits::XPATH_EXPRESSION_BYTE_CEILING,
1615 |p| &mut p.max_xpath_expression_bytes,
1616 ),
1617 (
1618 resource_name::XPATH_EXPRESSION_COMPLEXITY,
1619 crate::hard_limits::XPATH_EXPRESSION_COMPLEXITY_CEILING,
1620 |p| &mut p.max_xpath_expression_complexity,
1621 ),
1622 (
1623 resource_name::XPATH_CONTEXT_EVALUATIONS,
1624 crate::hard_limits::XPATH_CONTEXT_EVALUATION_CEILING,
1625 |p| &mut p.max_xpath_context_evaluations,
1626 ),
1627 (
1628 resource_name::XPATH_EVALUATION_WORK,
1629 crate::hard_limits::XPATH_EVALUATION_WORK_CEILING,
1630 |p| &mut p.max_xpath_evaluation_work,
1631 ),
1632 (
1633 resource_name::XPATH_MIRROR_STRING_BYTES,
1634 crate::hard_limits::XPATH_MIRROR_STRING_BYTE_CEILING,
1635 |p| &mut p.max_xpath_mirror_string_bytes,
1636 ),
1637 (
1638 resource_name::XPATH_STRING_WORK_BYTES,
1639 crate::hard_limits::XPATH_STRING_WORK_BYTE_CEILING,
1640 |p| &mut p.max_xpath_string_work_bytes,
1641 ),
1642 (
1643 resource_name::XPATH_NAMESPACE_BINDINGS,
1644 crate::hard_limits::XPATH_NAMESPACE_BINDING_CEILING,
1645 |p| &mut p.max_xpath_namespace_bindings,
1646 ),
1647 (
1648 resource_name::XPATH_NAMESPACE_BYTES,
1649 crate::hard_limits::XPATH_NAMESPACE_BYTE_CEILING,
1650 |p| &mut p.max_xpath_namespace_bytes,
1651 ),
1652 (
1653 resource_name::XPATH_FILTERS,
1654 crate::hard_limits::XPATH_FILTER_COUNT_CEILING,
1655 |p| &mut p.max_xpath_filters,
1656 ),
1657 (
1658 resource_name::NODE_SET_FILTER_WORK,
1659 crate::hard_limits::NODE_SET_FILTER_WORK_CEILING,
1660 |p| &mut p.max_node_set_filter_work,
1661 ),
1662 (
1663 resource_name::NODE_SET_ENTRIES,
1664 crate::hard_limits::NODE_SET_ENTRY_CEILING,
1665 |p| &mut p.max_node_set_entries,
1666 ),
1667 (
1668 resource_name::NODE_SET_OWNED_STRING_BYTES,
1669 crate::hard_limits::NODE_SET_OWNED_STRING_BYTE_CEILING,
1670 |p| &mut p.max_node_set_owned_string_bytes,
1671 ),
1672 (
1673 resource_name::NODE_SET_CUMULATIVE_OWNED_STRING_BYTES,
1674 crate::hard_limits::NODE_SET_CUMULATIVE_OWNED_STRING_BYTE_CEILING,
1675 |p| &mut p.max_node_set_cumulative_owned_string_bytes,
1676 ),
1677 ];
1678
1679 for &(resource, ceiling, field) in cases {
1680 let mut policy = ResourcePolicy::default();
1681 let actual = ceiling.saturating_add(1);
1682 *field(&mut policy) = actual;
1683 assert_eq!(
1684 policy.validate(),
1685 Err(PolicyViolation::ResourceLimit {
1686 resource,
1687 maximum: ceiling,
1688 actual,
1689 }),
1690 "wrong hard-ceiling validation for {resource}",
1691 );
1692 }
1693 }
1694
1695 #[test]
1696 fn resource_policy_accepts_zero_as_a_deny_all_ceiling() {
1697 let policy = ResourcePolicy {
1700 max_xml_nodes: 0,
1701 max_xml_depth: 0,
1702 max_xml_namespace_bindings: 0,
1703 max_references: 0,
1704 max_signatures: 0,
1705 max_transforms_per_reference: 0,
1706 max_xml_base_components: 0,
1707 max_xml_base_resolution_bytes: 0,
1708 max_canonicalized_bytes: 0,
1709 max_external_resource_bytes: 0,
1710 max_external_resource_total_bytes: 0,
1711 max_encryption_plaintext_bytes: 0,
1712 max_xml_document_bytes: 0,
1713 max_xml_parse_work_bytes: 0,
1714 max_encryption_recipients: 0,
1715 max_encryption_metadata_bytes: 0,
1716 max_key_candidates: 0,
1717 max_key_import_kdf_work: 0,
1718 max_key_import_kdf_memory_bytes: 0,
1719 max_key_info_reference_depth: 0,
1720 max_base64_transform_input_bytes: 0,
1721 max_base64_transform_output_bytes: 0,
1722 max_xpath_expressions: 0,
1723 max_xpath_expression_bytes: 0,
1724 max_xpath_expression_complexity: 0,
1725 max_xpath_context_evaluations: 0,
1726 max_xpath_evaluation_work: 0,
1727 max_xpath_mirror_string_bytes: 0,
1728 max_xpath_string_work_bytes: 0,
1729 max_xpath_namespace_bindings: 0,
1730 max_xpath_namespace_bytes: 0,
1731 max_xpath_filters: 0,
1732 max_node_set_filter_work: 0,
1733 max_node_set_entries: 0,
1734 max_node_set_owned_string_bytes: 0,
1735 max_node_set_cumulative_owned_string_bytes: 0,
1736 };
1737
1738 assert_eq!(policy.validate(), Ok(()));
1739 }
1740
1741 #[cfg(any(feature = "xmldsig", feature = "xmlenc"))]
1742 #[test]
1743 fn node_set_policy_has_headroom_above_its_default() {
1744 let mut policy = ResourcePolicy::default();
1745 assert_eq!(policy.max_node_set_entries, 262_144);
1746 assert_eq!(policy.validate(), Ok(()));
1747
1748 policy.max_node_set_entries = 524_288;
1749 assert_eq!(policy.validate(), Ok(()));
1750
1751 policy.max_node_set_entries = 524_289;
1752 assert_eq!(
1753 policy.validate(),
1754 Err(PolicyViolation::ResourceLimit {
1755 resource: resource_name::NODE_SET_ENTRIES,
1756 maximum: 524_288,
1757 actual: 524_289,
1758 })
1759 );
1760 }
1761
1762 #[cfg(any(feature = "xmldsig", feature = "xmlenc"))]
1763 #[test]
1764 fn rsa_key_policy_enforces_structure_range_and_explicit_relaxation() {
1765 let secure = RsaKeyPolicy::default();
1766 assert!(matches!(
1767 secure.validate_components("test", &[0x80; 128], &[1, 0, 1]),
1768 Err(PolicyViolation::KeySize {
1769 minimum_bits: 2048,
1770 maximum_bits: 8192,
1771 actual_bits: 1024,
1772 ..
1773 })
1774 ));
1775 let mut short_2048_width = [0_u8; 256];
1776 short_2048_width[0] = 1;
1777 assert!(matches!(
1778 secure.validate_components("test", &short_2048_width, &[1, 0, 1]),
1779 Err(PolicyViolation::KeySize {
1780 minimum_bits: 2048,
1781 actual_bits: 2041,
1782 ..
1783 })
1784 ));
1785 assert!(matches!(
1786 secure.validate_components("test", &[1; 1025], &[1, 0, 1]),
1787 Err(PolicyViolation::KeySize {
1788 actual_bits: 8193,
1789 ..
1790 })
1791 ));
1792 assert!(matches!(
1793 secure.validate_components("test", &[0x80; 256], &[2]),
1794 Err(PolicyViolation::InvalidKeyMaterial { .. })
1795 ));
1796 assert_eq!(
1797 secure.validate_components("test", &[0x80; 256], &[0x80, 0, 0, 1]),
1798 Ok(256),
1799 "normalized RSA components encode the exponent as unsigned bytes"
1800 );
1801
1802 let compatibility = RsaKeyPolicy {
1803 minimum_modulus_bits: 1024,
1804 };
1805 assert_eq!(
1806 compatibility.validate_components("test", &[0x80; 128], &[1, 0, 1]),
1807 Ok(128)
1808 );
1809 assert!(
1810 RsaKeyPolicy {
1811 minimum_modulus_bits: 2047,
1812 }
1813 .validate()
1814 .is_err()
1815 );
1816 }
1817
1818 #[cfg(feature = "xmldsig")]
1819 #[test]
1820 fn mandatory_x509_limits_report_the_nonzero_requirement() {
1821 let policy = KeyTrustPolicy {
1824 max_x509_chain_depth: 0,
1825 ..KeyTrustPolicy::default()
1826 };
1827
1828 let error = policy.validate().expect_err("zero depth must be rejected");
1829 assert!(
1830 error.to_string().contains("must be nonzero"),
1831 "unexpected lower-bound diagnostic: {error}"
1832 );
1833 }
1834
1835 #[cfg(feature = "xmldsig")]
1836 #[test]
1837 fn custom_extended_key_purposes_require_valid_oid_arcs() {
1838 let mut policy = KeyTrustPolicy::default();
1841 policy
1842 .allowed_extended_key_usages
1843 .insert(ExtendedKeyPurpose::Other(vec![1, 40, 7]));
1844
1845 assert!(matches!(
1846 policy.validate(),
1847 Err(PolicyViolation::KeyTrust {
1848 reason: "custom extended key purposes must contain valid OID arcs",
1849 })
1850 ));
1851 }
1852
1853 #[cfg(feature = "xmldsig")]
1854 #[test]
1855 fn crl_checking_requires_x509_chain_validation() {
1856 let policy = KeyTrustPolicy {
1859 check_crls: true,
1860 ..KeyTrustPolicy::default()
1861 };
1862
1863 assert!(matches!(
1864 policy.validate(),
1865 Err(PolicyViolation::KeyTrust {
1866 reason: "CRL checking requires X.509 chain validation"
1867 })
1868 ));
1869 }
1870
1871 #[cfg(feature = "xmldsig")]
1872 #[test]
1873 fn legacy_signature_algorithms_require_independent_policy_opt_ins() {
1874 let legacy = [
1875 SignatureAlgorithm::RsaSha1,
1876 SignatureAlgorithm::DsaSha1,
1877 SignatureAlgorithm::HmacSha1,
1878 SignatureAlgorithm::EcdsaSha1,
1879 ];
1880 let mut policy = VerificationPolicy::default();
1881
1882 for algorithm in legacy {
1883 assert!(matches!(
1884 policy.check_signature_algorithm(algorithm),
1885 Err(PolicyViolation::Algorithm { .. })
1886 ));
1887 policy
1888 .key_trust
1889 .allowed_legacy_signature_algorithms
1890 .insert(algorithm);
1891 assert_eq!(policy.check_signature_algorithm(algorithm), Ok(()));
1892 policy
1893 .key_trust
1894 .allowed_legacy_signature_algorithms
1895 .remove(&algorithm);
1896 }
1897 }
1898
1899 #[cfg(feature = "xmldsig")]
1900 #[test]
1901 fn dsa_key_policy_enforces_configured_minimum_and_hard_ceiling() {
1902 let policy = DsaKeyPolicy::default();
1903
1904 assert!(matches!(
1905 policy.validate_modulus_bits(1024),
1906 Err(PolicyViolation::KeySize {
1907 key_type: "DSA",
1908 minimum_bits: 2048,
1909 maximum_bits: 3072,
1910 actual_bits: 1024,
1911 ..
1912 })
1913 ));
1914 assert_eq!(policy.validate_modulus_bits(2048), Ok(()));
1915 assert!(matches!(
1916 policy.validate_modulus_bits(4096),
1917 Err(PolicyViolation::KeySize {
1918 key_type: "DSA",
1919 minimum_bits: 2048,
1920 maximum_bits: 3072,
1921 actual_bits: 4096,
1922 ..
1923 })
1924 ));
1925 }
1926
1927 #[cfg(feature = "xmldsig")]
1928 #[test]
1929 fn hmac_output_policy_cannot_weaken_the_xmldsig_floor() {
1930 let compatibility = HmacPolicy {
1933 minimum_key_bits: 40,
1934 minimum_output_bits: 40,
1935 };
1936
1937 assert_eq!(
1938 compatibility.validate_output(SignatureAlgorithm::HmacSha1, 80),
1939 Ok(())
1940 );
1941 assert!(matches!(
1942 compatibility.validate_output(SignatureAlgorithm::HmacSha1, 72),
1943 Err(PolicyViolation::HmacOutputLength { minimum: 80, .. })
1944 ));
1945 assert!(matches!(
1946 compatibility.validate_output(SignatureAlgorithm::HmacSha256, 120),
1947 Err(PolicyViolation::HmacOutputLength { minimum: 128, .. })
1948 ));
1949 }
1950
1951 #[cfg(feature = "xmldsig")]
1952 #[test]
1953 fn documented_xmldsig_limits_match_hard_limits() {
1954 let docs = include_str!("../docs/xmldsig.md");
1957 let mib = 1024 * 1024;
1958 assert!(docs.contains(&format!(
1959 "Individual resources are limited to {} MiB",
1960 crate::hard_limits::EXTERNAL_RESOURCE_BYTE_CEILING / mib
1961 )));
1962 assert!(docs.contains(&format!(
1963 "complete map to {} MiB",
1964 crate::hard_limits::EXTERNAL_RESOURCE_TOTAL_BYTE_CEILING / mib
1965 )));
1966 assert!(docs.contains(&format!(
1967 "ceilings are {} components and {} MiB per operation",
1968 crate::hard_limits::XML_BASE_COMPONENT_CEILING,
1969 crate::hard_limits::XML_BASE_RESOLUTION_BYTE_CEILING / mib
1970 )));
1971 }
1972}