1#[cfg(any(feature = "xmldsig", feature = "xmlenc"))]
8use std::collections::HashSet;
9#[cfg(feature = "xmldsig")]
10use std::time::SystemTime;
11
12#[cfg(feature = "xmldsig")]
13use crate::xmldsig::{DigestAlgorithm, SignatureAlgorithm, UriTypeSet, XPathHereSemantics};
14#[cfg(feature = "xmlenc")]
15use crate::xmlenc::{
16 DataEncryptionAlgorithm, KeyTransportAlgorithm, KeyWrapAlgorithm, OaepDigestAlgorithm,
17};
18
19pub(crate) mod resource_name {
24 pub const XML_NODES: &str = "XML nodes";
25 pub const XML_DEPTH: &str = "XML element depth";
26 pub const SIGNATURE_REFERENCES: &str = "signature references";
27 pub const REFERENCE_TRANSFORMS: &str = "reference transforms";
28 pub const XML_BASE_COMPONENTS: &str = "XML Base components";
29 pub const XML_BASE_RESOLUTION_BYTES: &str = "XML Base resolution bytes";
30 pub const CANONICALIZED_BYTES: &str = "canonicalized bytes";
31 pub const EXTERNAL_RESOURCE_BYTES: &str = "external resource bytes";
32 pub const AGGREGATE_EXTERNAL_RESOURCE_BYTES: &str = "aggregate external resource bytes";
33 pub const ENCRYPTION_PLAINTEXT_BYTES: &str = "encryption plaintext bytes";
34 #[cfg(feature = "xmlenc")]
35 pub const AGGREGATE_ENCRYPTION_CIPHER_VALUE_BYTES: &str =
36 "aggregate encryption CipherValue bytes";
37 pub const XML_DOCUMENT: &str = "XML document";
38 pub const XML_PARSE_WORK_BYTES: &str = "cumulative XML parse-work bytes";
39 pub const ENCRYPTION_RECIPIENTS: &str = "encryption recipients";
40 pub const ENCRYPTION_METADATA_BYTES: &str = "encryption metadata bytes";
41 pub const KEY_CANDIDATES: &str = "key candidates";
42 pub const BASE64_TRANSFORM_INPUT_BYTES: &str = "Base64 transform input bytes";
43 pub const BASE64_TRANSFORM_OUTPUT_BYTES: &str = "Base64 transform output bytes";
44 pub const XPATH_EXPRESSIONS: &str = "XPath expressions";
45 pub const XPATH_EXPRESSION_BYTES: &str = "XPath expression bytes";
46 pub const XPATH_EXPRESSION_COMPLEXITY: &str = "XPath expression complexity";
47 pub const XPATH_CONTEXT_EVALUATIONS: &str = "XPath context evaluations";
48 pub const XPATH_EVALUATION_WORK: &str = "XPath evaluation work";
49 pub const XPATH_MIRROR_STRING_BYTES: &str = "XPath mirror string bytes";
50 pub const XPATH_STRING_WORK_BYTES: &str = "XPath string-processing work bytes";
51 pub const XPATH_NAMESPACE_BINDINGS: &str = "XPath namespace bindings";
52 pub const XPATH_NAMESPACE_BYTES: &str = "XPath namespace bytes";
53 pub const XPATH_FILTERS: &str = "XPath filters";
54 pub const NODE_SET_FILTER_WORK: &str = "node-set filter work";
55 pub const NODE_SET_ENTRIES: &str = "node-set entries";
56 pub const NODE_SET_OWNED_STRING_BYTES: &str = "node-set owned string bytes";
57 pub const NODE_SET_CUMULATIVE_OWNED_STRING_BYTES: &str =
58 "cumulative node-set owned string bytes";
59}
60
61#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
63#[non_exhaustive]
64pub enum PolicyViolation {
65 #[error("{operation} policy rejects algorithm {algorithm}")]
67 Algorithm {
68 operation: &'static str,
70 algorithm: String,
72 },
73 #[error("{resource} exceeds policy maximum {maximum}: got {actual}")]
75 ResourceLimit {
76 resource: &'static str,
78 maximum: usize,
80 actual: usize,
82 },
83 #[error("{resource} has invalid policy limit {actual}: {requirement}")]
85 InvalidResourceLimit {
86 resource: &'static str,
88 requirement: &'static str,
90 actual: usize,
92 },
93 #[error("key/trust policy rejected the operation: {reason}")]
95 KeyTrust {
96 reason: &'static str,
98 },
99 #[error("XML input policy rejected the operation: {reason}")]
101 XmlInput {
102 reason: &'static str,
104 },
105 #[error("{operation} URI policy rejected the operation: {reason}")]
107 Uri {
108 operation: &'static str,
110 reason: &'static str,
112 },
113 #[error(
115 "{operation} policy requires {key_type} keys between {minimum_bits} and {maximum_bits} bits: got {actual_bits}"
116 )]
117 KeySize {
118 operation: &'static str,
120 key_type: &'static str,
122 minimum_bits: usize,
124 maximum_bits: usize,
126 actual_bits: usize,
128 },
129 #[error("{operation} policy rejects invalid {key_type} key material: {reason}")]
131 InvalidKeyMaterial {
132 operation: &'static str,
134 key_type: &'static str,
136 reason: &'static str,
138 },
139}
140
141#[cfg(any(feature = "xmldsig", feature = "xmlenc"))]
143#[derive(Debug, Clone, Copy, PartialEq, Eq)]
144pub struct RsaKeyPolicy {
145 pub minimum_modulus_bits: usize,
147}
148
149#[cfg(feature = "xmldsig")]
151#[derive(Debug, Clone, Copy, PartialEq, Eq)]
152pub struct DsaKeyPolicy {
153 pub minimum_modulus_bits: usize,
155}
156
157#[cfg(feature = "xmldsig")]
158impl Default for DsaKeyPolicy {
159 fn default() -> Self {
160 Self {
161 minimum_modulus_bits: 2048,
162 }
163 }
164}
165
166#[cfg(feature = "xmldsig")]
167impl DsaKeyPolicy {
168 pub fn validate(&self) -> Result<(), PolicyViolation> {
170 if self.minimum_modulus_bits == 0 || !self.minimum_modulus_bits.is_multiple_of(64) {
171 return Err(PolicyViolation::InvalidResourceLimit {
172 resource: "minimum DSA modulus bits",
173 requirement: "minimum must be a nonzero multiple of 64 bits",
174 actual: self.minimum_modulus_bits,
175 });
176 }
177 ResourcePolicy::within(
178 "minimum DSA modulus bits",
179 self.minimum_modulus_bits,
180 crate::hard_limits::DSA_MODULUS_BIT_CEILING,
181 )
182 }
183
184 pub(crate) fn validate_modulus_bits(&self, actual_bits: usize) -> Result<(), PolicyViolation> {
185 self.validate()?;
186 if !(self.minimum_modulus_bits..=crate::hard_limits::DSA_MODULUS_BIT_CEILING)
187 .contains(&actual_bits)
188 {
189 return Err(PolicyViolation::KeySize {
190 operation: "verification",
191 key_type: "DSA",
192 minimum_bits: self.minimum_modulus_bits,
193 maximum_bits: crate::hard_limits::DSA_MODULUS_BIT_CEILING,
194 actual_bits,
195 });
196 }
197 Ok(())
198 }
199}
200
201#[cfg(any(feature = "xmldsig", feature = "xmlenc"))]
202impl Default for RsaKeyPolicy {
203 fn default() -> Self {
204 Self {
205 minimum_modulus_bits: 2048,
206 }
207 }
208}
209
210#[cfg(any(feature = "xmldsig", feature = "xmlenc"))]
211impl RsaKeyPolicy {
212 pub fn validate(&self) -> Result<(), PolicyViolation> {
214 if self.minimum_modulus_bits == 0 || !self.minimum_modulus_bits.is_multiple_of(8) {
215 return Err(PolicyViolation::InvalidResourceLimit {
216 resource: "minimum RSA modulus bits",
217 requirement: "minimum must be a nonzero whole-byte width",
218 actual: self.minimum_modulus_bits,
219 });
220 }
221 ResourcePolicy::within(
222 "minimum RSA modulus bits",
223 self.minimum_modulus_bits,
224 crate::hard_limits::RSA_MODULUS_BIT_CEILING,
225 )
226 }
227
228 pub(crate) fn validate_components(
229 &self,
230 operation: &'static str,
231 modulus: &[u8],
232 exponent: &[u8],
233 ) -> Result<usize, PolicyViolation> {
234 self.validate()?;
235 let modulus = modulus
236 .iter()
237 .position(|byte| *byte != 0)
238 .map(|start| &modulus[start..])
239 .ok_or(PolicyViolation::InvalidKeyMaterial {
240 operation,
241 key_type: "RSA",
242 reason: "modulus is zero",
243 })?;
244 let modulus_bits = modulus
245 .len()
246 .checked_mul(8)
247 .and_then(|width| width.checked_sub(modulus[0].leading_zeros() as usize))
248 .ok_or(PolicyViolation::InvalidKeyMaterial {
249 operation,
250 key_type: "RSA",
251 reason: "modulus width overflows",
252 })?;
253 if !(self.minimum_modulus_bits..=crate::hard_limits::RSA_MODULUS_BIT_CEILING)
254 .contains(&modulus_bits)
255 {
256 return Err(PolicyViolation::KeySize {
257 operation,
258 key_type: "RSA",
259 minimum_bits: self.minimum_modulus_bits,
260 maximum_bits: crate::hard_limits::RSA_MODULUS_BIT_CEILING,
261 actual_bits: modulus_bits,
262 });
263 }
264 if exponent.is_empty() || exponent.len() > 8 {
265 return Err(PolicyViolation::InvalidKeyMaterial {
266 operation,
267 key_type: "RSA",
268 reason: "public exponent has invalid encoding",
269 });
270 }
271 let mut exponent_bytes = [0_u8; 8];
272 exponent_bytes[8 - exponent.len()..].copy_from_slice(exponent);
273 let exponent = u64::from_be_bytes(exponent_bytes);
274 if !(3..=((1_u64 << 33) - 1)).contains(&exponent) || exponent % 2 == 0 {
275 return Err(PolicyViolation::InvalidKeyMaterial {
276 operation,
277 key_type: "RSA",
278 reason: "public exponent is outside the supported odd range",
279 });
280 }
281 Ok(modulus.len())
282 }
283}
284
285#[derive(Debug, Clone, PartialEq, Eq)]
287pub struct ResourcePolicy {
288 pub max_xml_nodes: usize,
290 pub max_xml_depth: usize,
292 pub max_references: usize,
294 pub max_transforms_per_reference: usize,
296 pub max_xml_base_components: usize,
298 pub max_xml_base_resolution_bytes: usize,
300 pub max_canonicalized_bytes: usize,
302 pub max_external_resource_bytes: usize,
304 pub max_external_resource_total_bytes: usize,
306 pub max_encryption_plaintext_bytes: usize,
308 pub max_xml_document_bytes: usize,
310 pub max_xml_parse_work_bytes: usize,
312 pub max_encryption_recipients: usize,
314 pub max_encryption_metadata_bytes: usize,
316 pub max_key_candidates: usize,
318 pub max_base64_transform_input_bytes: usize,
320 pub max_base64_transform_output_bytes: usize,
322 pub max_xpath_expressions: usize,
324 pub max_xpath_expression_bytes: usize,
326 pub max_xpath_expression_complexity: usize,
328 pub max_xpath_context_evaluations: usize,
330 pub max_xpath_evaluation_work: usize,
332 pub max_xpath_mirror_string_bytes: usize,
334 pub max_xpath_string_work_bytes: usize,
336 pub max_xpath_namespace_bindings: usize,
338 pub max_xpath_namespace_bytes: usize,
340 pub max_xpath_filters: usize,
342 pub max_node_set_filter_work: usize,
344 pub max_node_set_entries: usize,
346 pub max_node_set_owned_string_bytes: usize,
348 pub max_node_set_cumulative_owned_string_bytes: usize,
350}
351
352impl Default for ResourcePolicy {
353 fn default() -> Self {
354 Self {
355 max_xml_nodes: crate::hard_limits::XML_DOCUMENT_NODE_CEILING as usize,
356 max_xml_depth: crate::hard_limits::XML_DOCUMENT_DEPTH_CEILING,
357 max_references: crate::hard_limits::SIGNATURE_REFERENCE_CEILING,
358 max_transforms_per_reference: crate::hard_limits::REFERENCE_TRANSFORM_CEILING,
359 max_xml_base_components: crate::hard_limits::XML_BASE_COMPONENT_CEILING,
360 max_xml_base_resolution_bytes: crate::hard_limits::XML_BASE_RESOLUTION_BYTE_CEILING,
361 max_canonicalized_bytes: crate::hard_limits::CANONICALIZED_SIGNATURE_DATA_BYTE_CEILING,
362 max_external_resource_bytes: crate::hard_limits::EXTERNAL_RESOURCE_BYTE_CEILING,
363 max_external_resource_total_bytes:
364 crate::hard_limits::EXTERNAL_RESOURCE_TOTAL_BYTE_CEILING,
365 max_encryption_plaintext_bytes: crate::hard_limits::ENCRYPTION_PLAINTEXT_BYTE_CEILING,
366 max_xml_document_bytes: crate::hard_limits::XML_DOCUMENT_BYTE_CEILING,
367 max_xml_parse_work_bytes: crate::hard_limits::XML_PARSE_WORK_BYTE_CEILING,
368 max_encryption_recipients: crate::hard_limits::ENCRYPTION_RECIPIENT_CEILING,
369 max_encryption_metadata_bytes: crate::hard_limits::ENCRYPTION_METADATA_BYTE_CEILING,
370 max_key_candidates: crate::hard_limits::KEY_CANDIDATE_CEILING,
371 max_base64_transform_input_bytes:
372 crate::hard_limits::BASE64_TRANSFORM_INPUT_BYTE_CEILING,
373 max_base64_transform_output_bytes:
374 crate::hard_limits::BASE64_TRANSFORM_OUTPUT_BYTE_CEILING,
375 max_xpath_expressions: crate::hard_limits::XPATH_EXPRESSION_COUNT_CEILING,
376 max_xpath_expression_bytes: crate::hard_limits::XPATH_EXPRESSION_BYTE_CEILING,
377 max_xpath_expression_complexity:
378 crate::hard_limits::XPATH_EXPRESSION_COMPLEXITY_CEILING,
379 max_xpath_context_evaluations: crate::hard_limits::XPATH_CONTEXT_EVALUATION_CEILING,
380 max_xpath_evaluation_work: crate::hard_limits::XPATH_EVALUATION_WORK_CEILING,
381 max_xpath_mirror_string_bytes: crate::hard_limits::XPATH_MIRROR_STRING_BYTE_CEILING,
382 max_xpath_string_work_bytes: crate::hard_limits::XPATH_STRING_WORK_BYTE_CEILING,
383 max_xpath_namespace_bindings: crate::hard_limits::XPATH_NAMESPACE_BINDING_CEILING,
384 max_xpath_namespace_bytes: crate::hard_limits::XPATH_NAMESPACE_BYTE_CEILING,
385 max_xpath_filters: crate::hard_limits::XPATH_FILTER_COUNT_CEILING,
386 max_node_set_filter_work: crate::hard_limits::NODE_SET_FILTER_WORK_CEILING,
387 max_node_set_entries: crate::hard_limits::NODE_SET_ENTRY_CEILING,
388 max_node_set_owned_string_bytes: crate::hard_limits::NODE_SET_OWNED_STRING_BYTE_CEILING,
389 max_node_set_cumulative_owned_string_bytes:
390 crate::hard_limits::NODE_SET_CUMULATIVE_OWNED_STRING_BYTE_CEILING,
391 }
392 }
393}
394
395impl ResourcePolicy {
396 pub fn validate(&self) -> Result<(), PolicyViolation> {
398 Self::within(
399 resource_name::XML_NODES,
400 self.max_xml_nodes,
401 crate::hard_limits::XML_DOCUMENT_NODE_CEILING as usize,
402 )?;
403 Self::within(
404 resource_name::XML_DEPTH,
405 self.max_xml_depth,
406 crate::hard_limits::XML_DOCUMENT_DEPTH_CEILING,
407 )?;
408 Self::within(
409 resource_name::CANONICALIZED_BYTES,
410 self.max_canonicalized_bytes,
411 crate::hard_limits::CANONICALIZED_SIGNATURE_DATA_BYTE_CEILING,
412 )?;
413 Self::within(
414 resource_name::SIGNATURE_REFERENCES,
415 self.max_references,
416 crate::hard_limits::SIGNATURE_REFERENCE_CEILING,
417 )?;
418 Self::within(
419 resource_name::REFERENCE_TRANSFORMS,
420 self.max_transforms_per_reference,
421 crate::hard_limits::REFERENCE_TRANSFORM_CEILING,
422 )?;
423 Self::within(
424 resource_name::XML_BASE_COMPONENTS,
425 self.max_xml_base_components,
426 crate::hard_limits::XML_BASE_COMPONENT_CEILING,
427 )?;
428 Self::within(
429 resource_name::XML_BASE_RESOLUTION_BYTES,
430 self.max_xml_base_resolution_bytes,
431 crate::hard_limits::XML_BASE_RESOLUTION_BYTE_CEILING,
432 )?;
433 Self::within(
434 resource_name::XML_DOCUMENT,
435 self.max_xml_document_bytes,
436 crate::hard_limits::XML_DOCUMENT_BYTE_CEILING,
437 )?;
438 Self::within(
439 resource_name::XML_PARSE_WORK_BYTES,
440 self.max_xml_parse_work_bytes,
441 crate::hard_limits::XML_PARSE_WORK_BYTE_CEILING,
442 )?;
443 Self::within(
444 resource_name::EXTERNAL_RESOURCE_BYTES,
445 self.max_external_resource_bytes,
446 crate::hard_limits::EXTERNAL_RESOURCE_BYTE_CEILING,
447 )?;
448 Self::within(
449 resource_name::AGGREGATE_EXTERNAL_RESOURCE_BYTES,
450 self.max_external_resource_total_bytes,
451 crate::hard_limits::EXTERNAL_RESOURCE_TOTAL_BYTE_CEILING,
452 )?;
453 Self::within(
454 resource_name::ENCRYPTION_PLAINTEXT_BYTES,
455 self.max_encryption_plaintext_bytes,
456 crate::hard_limits::ENCRYPTION_PLAINTEXT_BYTE_CEILING,
457 )?;
458 Self::within(
459 resource_name::ENCRYPTION_RECIPIENTS,
460 self.max_encryption_recipients,
461 crate::hard_limits::ENCRYPTION_RECIPIENT_CEILING,
462 )?;
463 Self::within(
464 resource_name::ENCRYPTION_METADATA_BYTES,
465 self.max_encryption_metadata_bytes,
466 crate::hard_limits::ENCRYPTION_METADATA_BYTE_CEILING,
467 )?;
468 for (resource, selected, ceiling) in [
469 (
470 resource_name::KEY_CANDIDATES,
471 self.max_key_candidates,
472 crate::hard_limits::KEY_CANDIDATE_CEILING,
473 ),
474 (
475 resource_name::BASE64_TRANSFORM_INPUT_BYTES,
476 self.max_base64_transform_input_bytes,
477 crate::hard_limits::BASE64_TRANSFORM_INPUT_BYTE_CEILING,
478 ),
479 (
480 resource_name::BASE64_TRANSFORM_OUTPUT_BYTES,
481 self.max_base64_transform_output_bytes,
482 crate::hard_limits::BASE64_TRANSFORM_OUTPUT_BYTE_CEILING,
483 ),
484 (
485 resource_name::XPATH_EXPRESSIONS,
486 self.max_xpath_expressions,
487 crate::hard_limits::XPATH_EXPRESSION_COUNT_CEILING,
488 ),
489 (
490 resource_name::XPATH_EXPRESSION_BYTES,
491 self.max_xpath_expression_bytes,
492 crate::hard_limits::XPATH_EXPRESSION_BYTE_CEILING,
493 ),
494 (
495 resource_name::XPATH_EXPRESSION_COMPLEXITY,
496 self.max_xpath_expression_complexity,
497 crate::hard_limits::XPATH_EXPRESSION_COMPLEXITY_CEILING,
498 ),
499 (
500 resource_name::XPATH_CONTEXT_EVALUATIONS,
501 self.max_xpath_context_evaluations,
502 crate::hard_limits::XPATH_CONTEXT_EVALUATION_CEILING,
503 ),
504 (
505 resource_name::XPATH_EVALUATION_WORK,
506 self.max_xpath_evaluation_work,
507 crate::hard_limits::XPATH_EVALUATION_WORK_CEILING,
508 ),
509 (
510 resource_name::XPATH_MIRROR_STRING_BYTES,
511 self.max_xpath_mirror_string_bytes,
512 crate::hard_limits::XPATH_MIRROR_STRING_BYTE_CEILING,
513 ),
514 (
515 resource_name::XPATH_STRING_WORK_BYTES,
516 self.max_xpath_string_work_bytes,
517 crate::hard_limits::XPATH_STRING_WORK_BYTE_CEILING,
518 ),
519 (
520 resource_name::XPATH_NAMESPACE_BINDINGS,
521 self.max_xpath_namespace_bindings,
522 crate::hard_limits::XPATH_NAMESPACE_BINDING_CEILING,
523 ),
524 (
525 resource_name::XPATH_NAMESPACE_BYTES,
526 self.max_xpath_namespace_bytes,
527 crate::hard_limits::XPATH_NAMESPACE_BYTE_CEILING,
528 ),
529 (
530 resource_name::XPATH_FILTERS,
531 self.max_xpath_filters,
532 crate::hard_limits::XPATH_FILTER_COUNT_CEILING,
533 ),
534 (
535 resource_name::NODE_SET_FILTER_WORK,
536 self.max_node_set_filter_work,
537 crate::hard_limits::NODE_SET_FILTER_WORK_CEILING,
538 ),
539 (
540 resource_name::NODE_SET_ENTRIES,
541 self.max_node_set_entries,
542 crate::hard_limits::NODE_SET_ENTRY_CEILING,
543 ),
544 (
545 resource_name::NODE_SET_OWNED_STRING_BYTES,
546 self.max_node_set_owned_string_bytes,
547 crate::hard_limits::NODE_SET_OWNED_STRING_BYTE_CEILING,
548 ),
549 (
550 resource_name::NODE_SET_CUMULATIVE_OWNED_STRING_BYTES,
551 self.max_node_set_cumulative_owned_string_bytes,
552 crate::hard_limits::NODE_SET_CUMULATIVE_OWNED_STRING_BYTE_CEILING,
553 ),
554 ] {
555 Self::within(resource, selected, ceiling)?;
556 }
557 Ok(())
558 }
559
560 pub(crate) fn validate_xml_document_len(&self, actual: usize) -> Result<(), PolicyViolation> {
561 if actual > self.max_xml_document_bytes {
562 return Err(PolicyViolation::ResourceLimit {
563 resource: resource_name::XML_DOCUMENT,
564 maximum: self.max_xml_document_bytes,
565 actual,
566 });
567 }
568 Ok(())
569 }
570
571 #[cfg(any(feature = "xmldsig", feature = "xmlenc"))]
572 pub(crate) fn validate_key_candidates(&self, actual: usize) -> Result<(), PolicyViolation> {
573 if actual > self.max_key_candidates {
574 return Err(PolicyViolation::ResourceLimit {
575 resource: resource_name::KEY_CANDIDATES,
576 maximum: self.max_key_candidates,
577 actual,
578 });
579 }
580 Ok(())
581 }
582
583 pub(crate) fn effective_xml_nodes(&self) -> u32 {
584 u32::try_from(self.max_xml_nodes)
585 .unwrap_or(crate::hard_limits::XML_DOCUMENT_NODE_CEILING)
586 .min(crate::hard_limits::XML_DOCUMENT_NODE_CEILING)
587 }
588
589 #[cfg(feature = "xmldsig")]
590 pub(crate) fn effective_canonicalized_bytes(&self) -> usize {
591 self.max_canonicalized_bytes
592 .min(crate::hard_limits::CANONICALIZED_SIGNATURE_DATA_BYTE_CEILING)
593 }
594
595 #[cfg(feature = "xmldsig")]
596 pub(crate) fn effective_xml_base_components(&self) -> usize {
597 self.max_xml_base_components
598 .min(crate::hard_limits::XML_BASE_COMPONENT_CEILING)
599 }
600
601 #[cfg(feature = "xmldsig")]
602 pub(crate) fn effective_xml_base_resolution_bytes(&self) -> usize {
603 self.max_xml_base_resolution_bytes
604 .min(crate::hard_limits::XML_BASE_RESOLUTION_BYTE_CEILING)
605 }
606
607 fn within(
608 resource: &'static str,
609 selected: usize,
610 ceiling: usize,
611 ) -> Result<(), PolicyViolation> {
612 if selected > ceiling {
613 return Err(PolicyViolation::ResourceLimit {
614 resource,
615 maximum: ceiling,
616 actual: selected,
617 });
618 }
619 Ok(())
620 }
621
622 #[cfg(feature = "xmldsig")]
623 fn nonzero_within(
624 resource: &'static str,
625 selected: usize,
626 ceiling: usize,
627 ) -> Result<(), PolicyViolation> {
628 if selected == 0 {
629 return Err(PolicyViolation::InvalidResourceLimit {
630 resource,
631 requirement: "limit must be nonzero",
632 actual: selected,
633 });
634 }
635 Self::within(resource, selected, ceiling)
636 }
637}
638
639#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
641pub struct XmlInputPolicy {
642 pub allow_internal_dtd: bool,
644}
645
646#[cfg(feature = "xmldsig")]
648#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
649pub enum SameDocumentIdSemantics {
650 #[default]
652 Specification,
653 XmlSecBarename,
659 XmlSecVisa3d,
663}
664
665#[cfg(feature = "xmldsig")]
667#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
668pub enum EcdsaSignatureValueEncoding {
669 #[default]
671 XmlDsig,
672 XmlSecAsn1Der,
674}
675
676#[cfg(feature = "xmldsig")]
678#[derive(Debug, Clone, Default, PartialEq, Eq)]
679pub struct TransformPolicy {
680 pub allowed_algorithms: Option<HashSet<String>>,
682 pub xpath_here_semantics: XPathHereSemantics,
684 pub same_document_id_semantics: SameDocumentIdSemantics,
686}
687
688#[cfg(feature = "xmldsig")]
690#[derive(Debug, Clone, Copy, PartialEq, Eq)]
691pub struct UriPolicy {
692 pub references: UriTypeSet,
694 pub retrieval_methods: UriTypeSet,
696}
697
698#[cfg(feature = "xmldsig")]
699impl Default for UriPolicy {
700 fn default() -> Self {
701 Self {
702 references: UriTypeSet::SAME_DOCUMENT,
703 retrieval_methods: UriTypeSet::SAME_DOCUMENT,
704 }
705 }
706}
707
708#[cfg(feature = "xmldsig")]
710#[derive(Debug, Clone, Copy, PartialEq, Eq)]
711pub struct KeySourcePolicy {
712 pub preset_key: bool,
714 pub key_name: bool,
716 pub key_value: bool,
718 pub der_encoded_key_value: bool,
720 pub x509_data: bool,
722}
723
724#[cfg(feature = "xmldsig")]
725impl Default for KeySourcePolicy {
726 fn default() -> Self {
727 Self {
728 preset_key: true,
729 key_name: true,
730 key_value: true,
731 der_encoded_key_value: true,
732 x509_data: true,
733 }
734 }
735}
736
737#[cfg(feature = "xmldsig")]
739#[derive(Debug, Clone, PartialEq, Eq, Hash)]
740#[non_exhaustive]
741pub enum ExtendedKeyPurpose {
742 ServerAuth,
744 ClientAuth,
746 CodeSigning,
748 EmailProtection,
750 TimeStamping,
752 OcspSigning,
754 Other(Vec<u64>),
756}
757
758#[cfg(feature = "xmldsig")]
760#[derive(Debug, Clone, PartialEq, Eq)]
761pub struct KeyTrustPolicy {
762 pub verify_x509_chains: bool,
764 pub max_x509_chain_depth: usize,
766 pub max_x509_candidate_paths: usize,
768 pub allowed_legacy_signature_algorithms: HashSet<SignatureAlgorithm>,
770 pub rsa_keys: RsaKeyPolicy,
772 pub dsa_keys: DsaKeyPolicy,
774 pub allowed_extended_key_usages: HashSet<ExtendedKeyPurpose>,
780 pub check_crls: bool,
783 pub verification_time: Option<SystemTime>,
785}
786
787#[cfg(feature = "xmldsig")]
788impl Default for KeyTrustPolicy {
789 fn default() -> Self {
790 Self {
791 verify_x509_chains: false,
792 max_x509_chain_depth: crate::hard_limits::X509_CHAIN_DEPTH_CEILING,
793 max_x509_candidate_paths: crate::hard_limits::X509_CANDIDATE_PATH_CEILING,
794 allowed_legacy_signature_algorithms: HashSet::new(),
795 rsa_keys: RsaKeyPolicy::default(),
796 dsa_keys: DsaKeyPolicy::default(),
797 allowed_extended_key_usages: HashSet::new(),
798 check_crls: false,
799 verification_time: None,
800 }
801 }
802}
803
804#[cfg(feature = "xmldsig")]
805impl KeyTrustPolicy {
806 pub(crate) fn validate(&self) -> Result<(), PolicyViolation> {
807 if self.check_crls && !self.verify_x509_chains {
808 return Err(PolicyViolation::KeyTrust {
809 reason: "CRL checking requires X.509 chain validation",
810 });
811 }
812 if self
813 .allowed_extended_key_usages
814 .iter()
815 .any(|purpose| match purpose {
816 ExtendedKeyPurpose::Other(arcs) => {
817 arcs.len() < 2 || arcs[0] > 2 || (arcs[0] < 2 && arcs[1] > 39)
818 }
819 _ => false,
820 })
821 {
822 return Err(PolicyViolation::KeyTrust {
823 reason: "custom extended key purposes must contain valid OID arcs",
824 });
825 }
826 self.rsa_keys.validate()?;
827 self.dsa_keys.validate()?;
828 ResourcePolicy::nonzero_within(
829 "X.509 chain depth",
830 self.max_x509_chain_depth,
831 crate::hard_limits::X509_CHAIN_DEPTH_CEILING,
832 )?;
833 ResourcePolicy::nonzero_within(
834 "X.509 candidate paths",
835 self.max_x509_candidate_paths,
836 crate::hard_limits::X509_CANDIDATE_PATH_CEILING,
837 )
838 }
839}
840
841#[cfg(feature = "xmldsig")]
843#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
844pub enum ManifestProcessing {
845 #[default]
847 Ignore,
848 Process,
850}
851
852#[cfg(feature = "xmldsig")]
854#[derive(Debug, Clone, Default)]
855pub struct VerificationPolicy {
856 pub signature_algorithms: Option<HashSet<SignatureAlgorithm>>,
859 pub digest_algorithms: Option<HashSet<DigestAlgorithm>>,
861 pub ecdsa_signature_value_encoding: EcdsaSignatureValueEncoding,
863 pub key_trust: KeyTrustPolicy,
865 pub key_sources: KeySourcePolicy,
867 pub uris: UriPolicy,
869 pub transforms: TransformPolicy,
871 pub manifest_processing: ManifestProcessing,
873 pub xml: XmlInputPolicy,
875 pub resources: ResourcePolicy,
877}
878
879#[cfg(feature = "xmldsig")]
880impl VerificationPolicy {
881 pub fn validate(&self) -> Result<(), PolicyViolation> {
883 self.resources.validate()?;
884 self.key_trust.validate()
885 }
886
887 pub fn check_signature_algorithm(
889 &self,
890 algorithm: SignatureAlgorithm,
891 ) -> Result<(), PolicyViolation> {
892 if matches!(
893 algorithm,
894 SignatureAlgorithm::RsaSha1
895 | SignatureAlgorithm::DsaSha1
896 | SignatureAlgorithm::HmacSha1
897 ) && !self
898 .key_trust
899 .allowed_legacy_signature_algorithms
900 .contains(&algorithm)
901 {
902 return Err(PolicyViolation::Algorithm {
903 operation: "verification",
904 algorithm: algorithm.uri().to_string(),
905 });
906 }
907 if self
908 .signature_algorithms
909 .as_ref()
910 .is_some_and(|allowed| !allowed.contains(&algorithm))
911 {
912 return Err(PolicyViolation::Algorithm {
913 operation: "verification",
914 algorithm: algorithm.uri().to_string(),
915 });
916 }
917 Ok(())
918 }
919}
920
921#[cfg(feature = "xmldsig")]
923#[derive(Debug, Clone, Default)]
924pub struct SigningPolicy {
925 pub signature_algorithms: Option<HashSet<SignatureAlgorithm>>,
927 pub digest_algorithms: Option<HashSet<DigestAlgorithm>>,
929 pub ecdsa_signature_value_encoding: EcdsaSignatureValueEncoding,
931 pub rsa_keys: RsaKeyPolicy,
933 pub uris: UriPolicy,
936 pub transforms: TransformPolicy,
938 pub manifest_processing: ManifestProcessing,
940 pub xml: XmlInputPolicy,
942 pub resources: ResourcePolicy,
944}
945
946#[cfg(feature = "xmldsig")]
947impl SigningPolicy {
948 pub fn validate(&self) -> Result<(), PolicyViolation> {
950 self.resources.validate()?;
951 self.rsa_keys.validate()
952 }
953}
954
955#[cfg(feature = "xmlenc")]
957#[derive(Debug, Clone, Default)]
958pub struct EncryptionPolicy {
959 pub data_algorithms: Option<HashSet<DataEncryptionAlgorithm>>,
961 pub key_transport_algorithms: Option<HashSet<KeyTransportAlgorithm>>,
963 pub key_wrap_algorithms: Option<HashSet<KeyWrapAlgorithm>>,
965 pub oaep_digests: Option<HashSet<OaepDigestAlgorithm>>,
967 pub rsa_keys: RsaKeyPolicy,
969 pub xml: XmlInputPolicy,
971 pub resources: ResourcePolicy,
973}
974
975#[cfg(feature = "xmlenc")]
976impl EncryptionPolicy {
977 pub fn validate(&self) -> Result<(), PolicyViolation> {
979 self.resources.validate()?;
980 self.rsa_keys.validate()
981 }
982}
983
984#[cfg(feature = "xmlenc")]
986#[derive(Debug, Clone, Default)]
987pub struct DecryptionPolicy {
988 pub data_algorithms: Option<HashSet<DataEncryptionAlgorithm>>,
990 pub key_transport_algorithms: Option<HashSet<KeyTransportAlgorithm>>,
992 pub key_wrap_algorithms: Option<HashSet<KeyWrapAlgorithm>>,
994 pub oaep_digests: Option<HashSet<OaepDigestAlgorithm>>,
996 pub xml: XmlInputPolicy,
998 pub resources: ResourcePolicy,
1000}
1001
1002#[cfg(feature = "xmlenc")]
1003impl DecryptionPolicy {
1004 pub fn validate(&self) -> Result<(), PolicyViolation> {
1006 self.resources.validate()
1007 }
1008}
1009
1010#[cfg(test)]
1011mod tests {
1012 use super::*;
1013
1014 #[test]
1015 fn resource_policy_cannot_exceed_implementation_ceiling() {
1016 let policy = ResourcePolicy {
1017 max_xml_nodes: 100_001,
1018 ..ResourcePolicy::default()
1019 };
1020 assert!(matches!(
1021 policy.validate(),
1022 Err(PolicyViolation::ResourceLimit {
1023 resource: resource_name::XML_NODES,
1024 maximum: 100_000,
1025 actual: 100_001,
1026 })
1027 ));
1028 }
1029
1030 #[test]
1031 fn xml_parse_work_policy_cannot_exceed_implementation_ceiling() {
1032 let policy = ResourcePolicy {
1033 max_xml_parse_work_bytes: crate::hard_limits::XML_PARSE_WORK_BYTE_CEILING
1034 .saturating_add(1),
1035 ..ResourcePolicy::default()
1036 };
1037
1038 assert_eq!(
1039 policy.validate(),
1040 Err(PolicyViolation::ResourceLimit {
1041 resource: resource_name::XML_PARSE_WORK_BYTES,
1042 maximum: crate::hard_limits::XML_PARSE_WORK_BYTE_CEILING,
1043 actual: crate::hard_limits::XML_PARSE_WORK_BYTE_CEILING.saturating_add(1),
1044 })
1045 );
1046 }
1047
1048 #[test]
1049 fn every_resource_policy_field_obeys_its_hard_ceiling() {
1050 type Case = (&'static str, usize, fn(&mut ResourcePolicy) -> &mut usize);
1054 let cases: &[Case] = &[
1055 (
1056 resource_name::XML_NODES,
1057 crate::hard_limits::XML_DOCUMENT_NODE_CEILING as usize,
1058 |p| &mut p.max_xml_nodes,
1059 ),
1060 (
1061 resource_name::XML_DEPTH,
1062 crate::hard_limits::XML_DOCUMENT_DEPTH_CEILING,
1063 |p| &mut p.max_xml_depth,
1064 ),
1065 (
1066 resource_name::SIGNATURE_REFERENCES,
1067 crate::hard_limits::SIGNATURE_REFERENCE_CEILING,
1068 |p| &mut p.max_references,
1069 ),
1070 (
1071 resource_name::REFERENCE_TRANSFORMS,
1072 crate::hard_limits::REFERENCE_TRANSFORM_CEILING,
1073 |p| &mut p.max_transforms_per_reference,
1074 ),
1075 (
1076 resource_name::XML_BASE_COMPONENTS,
1077 crate::hard_limits::XML_BASE_COMPONENT_CEILING,
1078 |p| &mut p.max_xml_base_components,
1079 ),
1080 (
1081 resource_name::XML_BASE_RESOLUTION_BYTES,
1082 crate::hard_limits::XML_BASE_RESOLUTION_BYTE_CEILING,
1083 |p| &mut p.max_xml_base_resolution_bytes,
1084 ),
1085 (
1086 resource_name::CANONICALIZED_BYTES,
1087 crate::hard_limits::CANONICALIZED_SIGNATURE_DATA_BYTE_CEILING,
1088 |p| &mut p.max_canonicalized_bytes,
1089 ),
1090 (
1091 resource_name::EXTERNAL_RESOURCE_BYTES,
1092 crate::hard_limits::EXTERNAL_RESOURCE_BYTE_CEILING,
1093 |p| &mut p.max_external_resource_bytes,
1094 ),
1095 (
1096 resource_name::AGGREGATE_EXTERNAL_RESOURCE_BYTES,
1097 crate::hard_limits::EXTERNAL_RESOURCE_TOTAL_BYTE_CEILING,
1098 |p| &mut p.max_external_resource_total_bytes,
1099 ),
1100 (
1101 resource_name::ENCRYPTION_PLAINTEXT_BYTES,
1102 crate::hard_limits::ENCRYPTION_PLAINTEXT_BYTE_CEILING,
1103 |p| &mut p.max_encryption_plaintext_bytes,
1104 ),
1105 (
1106 resource_name::XML_DOCUMENT,
1107 crate::hard_limits::XML_DOCUMENT_BYTE_CEILING,
1108 |p| &mut p.max_xml_document_bytes,
1109 ),
1110 (
1111 resource_name::XML_PARSE_WORK_BYTES,
1112 crate::hard_limits::XML_PARSE_WORK_BYTE_CEILING,
1113 |p| &mut p.max_xml_parse_work_bytes,
1114 ),
1115 (
1116 resource_name::ENCRYPTION_RECIPIENTS,
1117 crate::hard_limits::ENCRYPTION_RECIPIENT_CEILING,
1118 |p| &mut p.max_encryption_recipients,
1119 ),
1120 (
1121 resource_name::ENCRYPTION_METADATA_BYTES,
1122 crate::hard_limits::ENCRYPTION_METADATA_BYTE_CEILING,
1123 |p| &mut p.max_encryption_metadata_bytes,
1124 ),
1125 (
1126 resource_name::KEY_CANDIDATES,
1127 crate::hard_limits::KEY_CANDIDATE_CEILING,
1128 |p| &mut p.max_key_candidates,
1129 ),
1130 (
1131 resource_name::BASE64_TRANSFORM_INPUT_BYTES,
1132 crate::hard_limits::BASE64_TRANSFORM_INPUT_BYTE_CEILING,
1133 |p| &mut p.max_base64_transform_input_bytes,
1134 ),
1135 (
1136 resource_name::BASE64_TRANSFORM_OUTPUT_BYTES,
1137 crate::hard_limits::BASE64_TRANSFORM_OUTPUT_BYTE_CEILING,
1138 |p| &mut p.max_base64_transform_output_bytes,
1139 ),
1140 (
1141 resource_name::XPATH_EXPRESSIONS,
1142 crate::hard_limits::XPATH_EXPRESSION_COUNT_CEILING,
1143 |p| &mut p.max_xpath_expressions,
1144 ),
1145 (
1146 resource_name::XPATH_EXPRESSION_BYTES,
1147 crate::hard_limits::XPATH_EXPRESSION_BYTE_CEILING,
1148 |p| &mut p.max_xpath_expression_bytes,
1149 ),
1150 (
1151 resource_name::XPATH_EXPRESSION_COMPLEXITY,
1152 crate::hard_limits::XPATH_EXPRESSION_COMPLEXITY_CEILING,
1153 |p| &mut p.max_xpath_expression_complexity,
1154 ),
1155 (
1156 resource_name::XPATH_CONTEXT_EVALUATIONS,
1157 crate::hard_limits::XPATH_CONTEXT_EVALUATION_CEILING,
1158 |p| &mut p.max_xpath_context_evaluations,
1159 ),
1160 (
1161 resource_name::XPATH_EVALUATION_WORK,
1162 crate::hard_limits::XPATH_EVALUATION_WORK_CEILING,
1163 |p| &mut p.max_xpath_evaluation_work,
1164 ),
1165 (
1166 resource_name::XPATH_MIRROR_STRING_BYTES,
1167 crate::hard_limits::XPATH_MIRROR_STRING_BYTE_CEILING,
1168 |p| &mut p.max_xpath_mirror_string_bytes,
1169 ),
1170 (
1171 resource_name::XPATH_STRING_WORK_BYTES,
1172 crate::hard_limits::XPATH_STRING_WORK_BYTE_CEILING,
1173 |p| &mut p.max_xpath_string_work_bytes,
1174 ),
1175 (
1176 resource_name::XPATH_NAMESPACE_BINDINGS,
1177 crate::hard_limits::XPATH_NAMESPACE_BINDING_CEILING,
1178 |p| &mut p.max_xpath_namespace_bindings,
1179 ),
1180 (
1181 resource_name::XPATH_NAMESPACE_BYTES,
1182 crate::hard_limits::XPATH_NAMESPACE_BYTE_CEILING,
1183 |p| &mut p.max_xpath_namespace_bytes,
1184 ),
1185 (
1186 resource_name::XPATH_FILTERS,
1187 crate::hard_limits::XPATH_FILTER_COUNT_CEILING,
1188 |p| &mut p.max_xpath_filters,
1189 ),
1190 (
1191 resource_name::NODE_SET_FILTER_WORK,
1192 crate::hard_limits::NODE_SET_FILTER_WORK_CEILING,
1193 |p| &mut p.max_node_set_filter_work,
1194 ),
1195 (
1196 resource_name::NODE_SET_ENTRIES,
1197 crate::hard_limits::NODE_SET_ENTRY_CEILING,
1198 |p| &mut p.max_node_set_entries,
1199 ),
1200 (
1201 resource_name::NODE_SET_OWNED_STRING_BYTES,
1202 crate::hard_limits::NODE_SET_OWNED_STRING_BYTE_CEILING,
1203 |p| &mut p.max_node_set_owned_string_bytes,
1204 ),
1205 (
1206 resource_name::NODE_SET_CUMULATIVE_OWNED_STRING_BYTES,
1207 crate::hard_limits::NODE_SET_CUMULATIVE_OWNED_STRING_BYTE_CEILING,
1208 |p| &mut p.max_node_set_cumulative_owned_string_bytes,
1209 ),
1210 ];
1211
1212 for &(resource, ceiling, field) in cases {
1213 let mut policy = ResourcePolicy::default();
1214 let actual = ceiling.saturating_add(1);
1215 *field(&mut policy) = actual;
1216 assert_eq!(
1217 policy.validate(),
1218 Err(PolicyViolation::ResourceLimit {
1219 resource,
1220 maximum: ceiling,
1221 actual,
1222 }),
1223 "wrong hard-ceiling validation for {resource}",
1224 );
1225 }
1226 }
1227
1228 #[test]
1229 fn resource_policy_accepts_zero_as_a_deny_all_ceiling() {
1230 let policy = ResourcePolicy {
1233 max_xml_nodes: 0,
1234 max_xml_depth: 0,
1235 max_references: 0,
1236 max_transforms_per_reference: 0,
1237 max_xml_base_components: 0,
1238 max_xml_base_resolution_bytes: 0,
1239 max_canonicalized_bytes: 0,
1240 max_external_resource_bytes: 0,
1241 max_external_resource_total_bytes: 0,
1242 max_encryption_plaintext_bytes: 0,
1243 max_xml_document_bytes: 0,
1244 max_xml_parse_work_bytes: 0,
1245 max_encryption_recipients: 0,
1246 max_encryption_metadata_bytes: 0,
1247 max_key_candidates: 0,
1248 max_base64_transform_input_bytes: 0,
1249 max_base64_transform_output_bytes: 0,
1250 max_xpath_expressions: 0,
1251 max_xpath_expression_bytes: 0,
1252 max_xpath_expression_complexity: 0,
1253 max_xpath_context_evaluations: 0,
1254 max_xpath_evaluation_work: 0,
1255 max_xpath_mirror_string_bytes: 0,
1256 max_xpath_string_work_bytes: 0,
1257 max_xpath_namespace_bindings: 0,
1258 max_xpath_namespace_bytes: 0,
1259 max_xpath_filters: 0,
1260 max_node_set_filter_work: 0,
1261 max_node_set_entries: 0,
1262 max_node_set_owned_string_bytes: 0,
1263 max_node_set_cumulative_owned_string_bytes: 0,
1264 };
1265
1266 assert_eq!(policy.validate(), Ok(()));
1267 }
1268
1269 #[cfg(any(feature = "xmldsig", feature = "xmlenc"))]
1270 #[test]
1271 fn rsa_key_policy_enforces_structure_range_and_explicit_relaxation() {
1272 let secure = RsaKeyPolicy::default();
1273 assert!(matches!(
1274 secure.validate_components("test", &[0x80; 128], &[1, 0, 1]),
1275 Err(PolicyViolation::KeySize {
1276 minimum_bits: 2048,
1277 maximum_bits: 8192,
1278 actual_bits: 1024,
1279 ..
1280 })
1281 ));
1282 let mut short_2048_width = [0_u8; 256];
1283 short_2048_width[0] = 1;
1284 assert!(matches!(
1285 secure.validate_components("test", &short_2048_width, &[1, 0, 1]),
1286 Err(PolicyViolation::KeySize {
1287 minimum_bits: 2048,
1288 actual_bits: 2041,
1289 ..
1290 })
1291 ));
1292 assert!(matches!(
1293 secure.validate_components("test", &[1; 1025], &[1, 0, 1]),
1294 Err(PolicyViolation::KeySize {
1295 actual_bits: 8193,
1296 ..
1297 })
1298 ));
1299 assert!(matches!(
1300 secure.validate_components("test", &[0x80; 256], &[2]),
1301 Err(PolicyViolation::InvalidKeyMaterial { .. })
1302 ));
1303 assert_eq!(
1304 secure.validate_components("test", &[0x80; 256], &[0x80, 0, 0, 1]),
1305 Ok(256),
1306 "normalized RSA components encode the exponent as unsigned bytes"
1307 );
1308
1309 let compatibility = RsaKeyPolicy {
1310 minimum_modulus_bits: 1024,
1311 };
1312 assert_eq!(
1313 compatibility.validate_components("test", &[0x80; 128], &[1, 0, 1]),
1314 Ok(128)
1315 );
1316 assert!(
1317 RsaKeyPolicy {
1318 minimum_modulus_bits: 2047,
1319 }
1320 .validate()
1321 .is_err()
1322 );
1323 }
1324
1325 #[cfg(feature = "xmldsig")]
1326 #[test]
1327 fn mandatory_x509_limits_report_the_nonzero_requirement() {
1328 let policy = KeyTrustPolicy {
1331 max_x509_chain_depth: 0,
1332 ..KeyTrustPolicy::default()
1333 };
1334
1335 let error = policy.validate().expect_err("zero depth must be rejected");
1336 assert!(
1337 error.to_string().contains("must be nonzero"),
1338 "unexpected lower-bound diagnostic: {error}"
1339 );
1340 }
1341
1342 #[cfg(feature = "xmldsig")]
1343 #[test]
1344 fn custom_extended_key_purposes_require_valid_oid_arcs() {
1345 let mut policy = KeyTrustPolicy::default();
1348 policy
1349 .allowed_extended_key_usages
1350 .insert(ExtendedKeyPurpose::Other(vec![1, 40, 7]));
1351
1352 assert!(matches!(
1353 policy.validate(),
1354 Err(PolicyViolation::KeyTrust {
1355 reason: "custom extended key purposes must contain valid OID arcs",
1356 })
1357 ));
1358 }
1359
1360 #[cfg(feature = "xmldsig")]
1361 #[test]
1362 fn crl_checking_requires_x509_chain_validation() {
1363 let policy = KeyTrustPolicy {
1366 check_crls: true,
1367 ..KeyTrustPolicy::default()
1368 };
1369
1370 assert!(matches!(
1371 policy.validate(),
1372 Err(PolicyViolation::KeyTrust {
1373 reason: "CRL checking requires X.509 chain validation"
1374 })
1375 ));
1376 }
1377
1378 #[cfg(feature = "xmldsig")]
1379 #[test]
1380 fn legacy_signature_algorithms_require_independent_policy_opt_ins() {
1381 let legacy = [
1382 SignatureAlgorithm::RsaSha1,
1383 SignatureAlgorithm::DsaSha1,
1384 SignatureAlgorithm::HmacSha1,
1385 ];
1386 let mut policy = VerificationPolicy::default();
1387
1388 for algorithm in legacy {
1389 assert!(matches!(
1390 policy.check_signature_algorithm(algorithm),
1391 Err(PolicyViolation::Algorithm { .. })
1392 ));
1393 policy
1394 .key_trust
1395 .allowed_legacy_signature_algorithms
1396 .insert(algorithm);
1397 assert_eq!(policy.check_signature_algorithm(algorithm), Ok(()));
1398 policy
1399 .key_trust
1400 .allowed_legacy_signature_algorithms
1401 .remove(&algorithm);
1402 }
1403 }
1404
1405 #[cfg(feature = "xmldsig")]
1406 #[test]
1407 fn dsa_key_policy_enforces_configured_minimum_and_hard_ceiling() {
1408 let policy = DsaKeyPolicy::default();
1409
1410 assert!(matches!(
1411 policy.validate_modulus_bits(1024),
1412 Err(PolicyViolation::KeySize {
1413 key_type: "DSA",
1414 minimum_bits: 2048,
1415 maximum_bits: 3072,
1416 actual_bits: 1024,
1417 ..
1418 })
1419 ));
1420 assert_eq!(policy.validate_modulus_bits(2048), Ok(()));
1421 assert!(matches!(
1422 policy.validate_modulus_bits(4096),
1423 Err(PolicyViolation::KeySize {
1424 key_type: "DSA",
1425 minimum_bits: 2048,
1426 maximum_bits: 3072,
1427 actual_bits: 4096,
1428 ..
1429 })
1430 ));
1431 }
1432
1433 #[cfg(feature = "xmldsig")]
1434 #[test]
1435 fn documented_xmldsig_limits_match_hard_limits() {
1436 let docs = include_str!("../docs/xmldsig.md");
1439 let mib = 1024 * 1024;
1440 assert!(docs.contains(&format!(
1441 "Individual resources are limited to {} MiB",
1442 crate::hard_limits::EXTERNAL_RESOURCE_BYTE_CEILING / mib
1443 )));
1444 assert!(docs.contains(&format!(
1445 "complete map to {} MiB",
1446 crate::hard_limits::EXTERNAL_RESOURCE_TOTAL_BYTE_CEILING / mib
1447 )));
1448 assert!(docs.contains(&format!(
1449 "ceilings are {} components and {} MiB per operation",
1450 crate::hard_limits::XML_BASE_COMPONENT_CEILING,
1451 crate::hard_limits::XML_BASE_RESOLUTION_BYTE_CEILING / mib
1452 )));
1453 }
1454}