Skip to main content

xml_sec/
policy.rs

1//! Immutable security policy snapshots shared by XML Security operations.
2//!
3//! Policy contains trusted, reusable decisions. Caller-owned keys, selected
4//! document targets, tenant identity, and external resource bytes remain in
5//! operation request contexts and are deliberately not stored here.
6
7#[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
19#[cfg(feature = "xmldsig")]
20mod key_establishment;
21#[cfg(feature = "xmldsig")]
22pub use key_establishment::{
23    KemContentAuthentication, KeyAgreementAlgorithm, KeyDerivationAlgorithm, KeyEstablishmentPolicy,
24};
25
26/// Permission for the unbounded parameterized RSA-PSS family.
27/// Exact permissions continue to use the signature algorithm allowlist.
28#[cfg(feature = "xmldsig")]
29#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
30pub enum RsaPssPermission {
31    /// No additional family grant; existing allowlists and secure defaults apply.
32    #[default]
33    ExactOnly,
34    /// Permit every supported tuple; capability and key restrictions still apply.
35    AllSupported,
36}
37
38#[cfg(feature = "xmldsig")]
39impl RsaPssPermission {
40    fn permits(self, algorithm: SignatureAlgorithm) -> bool {
41        self == Self::AllSupported && matches!(algorithm, SignatureAlgorithm::RsaPss(_))
42    }
43}
44
45/// Canonical diagnostics for limits represented by [`ResourcePolicy`].
46///
47/// Validation and every enforcement point use the same names so callers can
48/// match typed policy violations without operation-specific string drift.
49pub(crate) mod resource_name {
50    pub const XML_NODES: &str = "XML nodes";
51    pub const XML_DEPTH: &str = "XML element depth";
52    pub const XML_NAMESPACE_BINDINGS: &str = "XML namespace bindings";
53    pub const SIGNATURE_REFERENCES: &str = "signature references";
54    #[cfg(feature = "xmlenc")]
55    pub const ENCRYPTION_REFERENCES: &str = "encryption key references";
56    pub const VERIFICATION_SIGNATURES: &str = "verification signatures";
57    pub const REFERENCE_TRANSFORMS: &str = "reference transforms";
58    pub const XML_BASE_COMPONENTS: &str = "XML Base components";
59    pub const XML_BASE_RESOLUTION_BYTES: &str = "XML Base resolution bytes";
60    pub const CANONICALIZED_BYTES: &str = "canonicalized bytes";
61    pub const EXTERNAL_RESOURCE_BYTES: &str = "external resource bytes";
62    pub const AGGREGATE_EXTERNAL_RESOURCE_BYTES: &str = "aggregate external resource bytes";
63    pub const ENCRYPTION_PLAINTEXT_BYTES: &str = "encryption plaintext bytes";
64    #[cfg(feature = "xmlenc")]
65    pub const AGGREGATE_ENCRYPTION_CIPHER_VALUE_BYTES: &str =
66        "aggregate encryption CipherValue bytes";
67    pub const XML_DOCUMENT: &str = "XML document";
68    pub const XML_PARSE_WORK_BYTES: &str = "cumulative XML parse-work bytes";
69    pub const ENCRYPTION_RECIPIENTS: &str = "encryption recipients";
70    pub const ENCRYPTION_METADATA_BYTES: &str = "encryption metadata bytes";
71    pub const KEY_CANDIDATES: &str = "key candidates";
72    pub const KEY_IMPORT_KDF_WORK: &str = "key import KDF work";
73    pub const KEY_IMPORT_KDF_MEMORY: &str = "key import KDF memory bytes";
74    #[cfg(feature = "xmldsig")]
75    pub const KEY_ESTABLISHMENT_HASH_BLOCKS: &str = "key establishment hash blocks";
76    #[cfg(feature = "xmldsig")]
77    pub const KEY_ESTABLISHMENT_OWNED_BYTES: &str = "key establishment owned bytes";
78    #[cfg(feature = "xmldsig")]
79    pub const DH_MODULUS_BITS: &str = "DH modulus bits";
80    #[cfg(feature = "xmldsig")]
81    pub const DH_SUBGROUP_BITS: &str = "DH subgroup bits";
82    #[cfg(feature = "xmldsig")]
83    pub const KEY_ESTABLISHMENT_MODULAR_WORK: &str = "key establishment modular work";
84    pub const KEY_INFO_REFERENCE_DEPTH: &str = "KeyInfoReference depth";
85    pub const BASE64_TRANSFORM_INPUT_BYTES: &str = "Base64 transform input bytes";
86    pub const BASE64_TRANSFORM_OUTPUT_BYTES: &str = "Base64 transform output bytes";
87    pub const XPATH_EXPRESSIONS: &str = "XPath expressions";
88    pub const XPATH_EXPRESSION_BYTES: &str = "XPath expression bytes";
89    pub const XPATH_EXPRESSION_COMPLEXITY: &str = "XPath expression complexity";
90    pub const XPATH_CONTEXT_EVALUATIONS: &str = "XPath context evaluations";
91    pub const XPATH_EVALUATION_WORK: &str = "XPath evaluation work";
92    pub const XPATH_MIRROR_STRING_BYTES: &str = "XPath mirror string bytes";
93    pub const XPATH_STRING_WORK_BYTES: &str = "XPath string-processing work bytes";
94    pub const XPATH_NAMESPACE_BINDINGS: &str = "XPath namespace bindings";
95    pub const XPATH_NAMESPACE_BYTES: &str = "XPath namespace bytes";
96    pub const XPATH_FILTERS: &str = "XPath filters";
97    pub const NODE_SET_FILTER_WORK: &str = "node-set filter work";
98    pub const OPC_WORKSPACE_BYTES: &str = "OPC workspace bytes";
99    pub const OPC_PARAMETER_BYTES: &str = "OPC parameter bytes";
100    pub const NODE_SET_ENTRIES: &str = "node-set entries";
101    pub const NODE_SET_OWNED_STRING_BYTES: &str = "node-set owned string bytes";
102    pub const NODE_SET_CUMULATIVE_OWNED_STRING_BYTES: &str =
103        "cumulative node-set owned string bytes";
104}
105
106/// A typed rejection produced by an operation policy.
107#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
108#[non_exhaustive]
109pub enum PolicyViolation {
110    /// An algorithm is outside the operation allowlist.
111    #[error("{operation} policy rejects algorithm {algorithm}")]
112    Algorithm {
113        /// Operation evaluating the algorithm.
114        operation: &'static str,
115        /// Stable algorithm URI or diagnostic name.
116        algorithm: String,
117    },
118    /// An HMAC output length is outside the operation's configured bounds.
119    #[cfg(feature = "xmldsig")]
120    #[error("HMAC output length must be between {minimum} and {maximum} bits: got {actual}")]
121    HmacOutputLength {
122        /// Minimum output length selected by policy.
123        minimum: usize,
124        /// Full output width of the selected HMAC algorithm.
125        maximum: usize,
126        /// Parsed output length.
127        actual: usize,
128    },
129    /// An input exceeds a configured resource ceiling.
130    #[error("{resource} exceeds policy maximum {maximum}: got {actual}")]
131    ResourceLimit {
132        /// Resource whose consumption was rejected.
133        resource: &'static str,
134        /// Effective policy ceiling.
135        maximum: usize,
136        /// Observed consumption.
137        actual: usize,
138    },
139    /// An import exceeded a resource ceiling, but its parser did not expose the measured value.
140    #[error("{resource} exceeds policy maximum {maximum}")]
141    ResourceLimitExceeded {
142        /// Resource whose consumption was rejected.
143        resource: &'static str,
144        /// Effective policy ceiling.
145        maximum: usize,
146    },
147    /// A protected import supplied zero or too many KDF iterations; the parser did not expose the count.
148    #[error("key import KDF iterations must be between 1 and {maximum}")]
149    KdfIterationsOutsideLimit {
150        /// Effective iteration ceiling.
151        maximum: usize,
152    },
153    /// A configured resource limit violates a structural policy requirement.
154    #[error("{resource} has invalid policy limit {actual}: {requirement}")]
155    InvalidResourceLimit {
156        /// Resource whose configured limit was rejected.
157        resource: &'static str,
158        /// Required property of the configured limit.
159        requirement: &'static str,
160        /// Rejected configured value.
161        actual: usize,
162    },
163    /// The selected key source or trust mode is disallowed.
164    #[error("key/trust policy rejected the operation: {reason}")]
165    KeyTrust {
166        /// Non-secret reason suitable for diagnostics.
167        reason: &'static str,
168    },
169    /// XML parser behavior is disallowed.
170    #[error("XML input policy rejected the operation: {reason}")]
171    XmlInput {
172        /// Non-secret reason suitable for diagnostics.
173        reason: &'static str,
174    },
175    /// A URI class is outside the operation policy.
176    #[error("{operation} URI policy rejected the operation: {reason}")]
177    Uri {
178        /// Operation evaluating the URI.
179        operation: &'static str,
180        /// Non-sensitive reason suitable for diagnostics.
181        reason: &'static str,
182    },
183    /// An asymmetric key/domain falls outside the operation's strength range.
184    #[error(
185        "{operation} policy requires {key_type} keys between {minimum_bits} and {maximum_bits} bits: got {actual_bits}"
186    )]
187    KeySize {
188        /// Operation evaluating the key.
189        operation: &'static str,
190        /// Stable key-family diagnostic.
191        key_type: &'static str,
192        /// Configured minimum width of the named key/domain component.
193        minimum_bits: usize,
194        /// Effective maximum, bounded by the implementation ceiling.
195        maximum_bits: usize,
196        /// Observed normalized width of the named key/domain component.
197        actual_bits: usize,
198    },
199    /// RSA key material is structurally invalid.
200    #[error("{operation} policy rejects invalid {key_type} key material: {reason}")]
201    InvalidKeyMaterial {
202        /// Operation evaluating the key.
203        operation: &'static str,
204        /// Stable key-family diagnostic.
205        key_type: &'static str,
206        /// Non-secret structural rejection reason.
207        reason: &'static str,
208    },
209}
210
211/// HMAC key-strength and truncation requirements shared by signing and verification.
212#[cfg(feature = "xmldsig")]
213#[derive(Debug, Clone, Copy, PartialEq, Eq)]
214pub struct HmacPolicy {
215    /// Minimum caller-owned secret length in bits.
216    pub minimum_key_bits: usize,
217    /// Minimum emitted or accepted MAC length in bits.
218    pub minimum_output_bits: usize,
219}
220
221#[cfg(feature = "xmldsig")]
222impl Default for HmacPolicy {
223    fn default() -> Self {
224        Self {
225            minimum_key_bits: 128,
226            minimum_output_bits: 128,
227        }
228    }
229}
230
231#[cfg(feature = "xmldsig")]
232impl HmacPolicy {
233    pub(crate) fn validate(self) -> Result<(), PolicyViolation> {
234        if self.minimum_key_bits == 0 || self.minimum_output_bits == 0 {
235            return Err(PolicyViolation::KeyTrust {
236                reason: "HMAC minimum key and output lengths must be nonzero",
237            });
238        }
239        Ok(())
240    }
241
242    pub(crate) fn validate_key_bytes(self, key_bytes: usize) -> Result<(), PolicyViolation> {
243        self.validate_key_bits(key_bytes.saturating_mul(8))
244    }
245
246    pub(crate) fn validate_key_bits(self, key_bits: usize) -> Result<(), PolicyViolation> {
247        if key_bits < self.minimum_key_bits {
248            return Err(PolicyViolation::InvalidKeyMaterial {
249                operation: "HMAC",
250                key_type: "symmetric",
251                reason: "secret is shorter than the configured minimum",
252            });
253        }
254        Ok(())
255    }
256
257    pub(crate) fn validate_output(
258        self,
259        algorithm: SignatureAlgorithm,
260        selected_bits: usize,
261    ) -> Result<(), PolicyViolation> {
262        let maximum = algorithm
263            .hmac_output_bits()
264            .ok_or_else(|| PolicyViolation::Algorithm {
265                operation: "HMAC",
266                algorithm: algorithm.uri().to_owned(),
267            })?;
268        // XMLDSig 1.1 section 4.4.2 makes this a protocol floor, not a
269        // deployment preference: truncation is at least 80 bits and at least
270        // half the underlying digest width. Caller policy may only tighten it.
271        // https://www.w3.org/TR/2013/REC-xmldsig-core1-20130411/#sec-SignatureMethod
272        let minimum = self.minimum_output_bits.max(80).max(maximum / 2);
273        if selected_bits < minimum || selected_bits > maximum {
274            return Err(PolicyViolation::HmacOutputLength {
275                minimum,
276                maximum,
277                actual: selected_bits,
278            });
279        }
280        Ok(())
281    }
282}
283
284/// RSA strength and structural requirements for cryptographic operations.
285#[cfg(any(feature = "xmldsig", feature = "xmlenc"))]
286#[derive(Debug, Clone, Copy, PartialEq, Eq)]
287pub struct RsaKeyPolicy {
288    /// Minimum mathematical RSA modulus bit length accepted by the operation.
289    pub minimum_modulus_bits: usize,
290}
291
292/// DSA strength requirements for legacy signature verification.
293#[cfg(feature = "xmldsig")]
294#[derive(Debug, Clone, Copy, PartialEq, Eq)]
295pub struct DsaKeyPolicy {
296    /// Minimum prime-modulus width accepted for DSA verification.
297    pub minimum_modulus_bits: usize,
298}
299
300#[cfg(feature = "xmldsig")]
301impl Default for DsaKeyPolicy {
302    fn default() -> Self {
303        Self {
304            minimum_modulus_bits: 2048,
305        }
306    }
307}
308
309#[cfg(feature = "xmldsig")]
310impl DsaKeyPolicy {
311    /// Validate the configured minimum against the implementation ceiling.
312    pub fn validate(&self) -> Result<(), PolicyViolation> {
313        validate_modulus_minimum(
314            "minimum DSA modulus bits",
315            self.minimum_modulus_bits,
316            64,
317            "minimum must be a nonzero multiple of 64 bits",
318            crate::hard_limits::DSA_MODULUS_BIT_CEILING,
319        )
320    }
321
322    pub(crate) fn validate_modulus_bits(&self, actual_bits: usize) -> Result<(), PolicyViolation> {
323        self.validate()?;
324        if !(self.minimum_modulus_bits..=crate::hard_limits::DSA_MODULUS_BIT_CEILING)
325            .contains(&actual_bits)
326        {
327            return Err(PolicyViolation::KeySize {
328                operation: "verification",
329                key_type: "DSA",
330                minimum_bits: self.minimum_modulus_bits,
331                maximum_bits: crate::hard_limits::DSA_MODULUS_BIT_CEILING,
332                actual_bits,
333            });
334        }
335        Ok(())
336    }
337}
338
339#[cfg(any(feature = "xmldsig", feature = "xmlenc"))]
340impl Default for RsaKeyPolicy {
341    fn default() -> Self {
342        Self {
343            minimum_modulus_bits: 2048,
344        }
345    }
346}
347
348#[cfg(any(feature = "xmldsig", feature = "xmlenc"))]
349impl RsaKeyPolicy {
350    /// Validate the configured minimum against the implementation ceiling.
351    pub fn validate(&self) -> Result<(), PolicyViolation> {
352        validate_modulus_minimum(
353            "minimum RSA modulus bits",
354            self.minimum_modulus_bits,
355            8,
356            "minimum must be a nonzero whole-byte width",
357            crate::hard_limits::RSA_MODULUS_BIT_CEILING,
358        )
359    }
360
361    pub(crate) fn validate_components(
362        &self,
363        operation: &'static str,
364        modulus: &[u8],
365        exponent: &[u8],
366    ) -> Result<usize, PolicyViolation> {
367        self.validate()?;
368        let modulus = modulus
369            .iter()
370            .position(|byte| *byte != 0)
371            .map(|start| &modulus[start..])
372            .ok_or(PolicyViolation::InvalidKeyMaterial {
373                operation,
374                key_type: "RSA",
375                reason: "modulus is zero",
376            })?;
377        let modulus_bits = modulus
378            .len()
379            .checked_mul(8)
380            .and_then(|width| width.checked_sub(modulus[0].leading_zeros() as usize))
381            .ok_or(PolicyViolation::InvalidKeyMaterial {
382                operation,
383                key_type: "RSA",
384                reason: "modulus width overflows",
385            })?;
386        let exponent = if exponent.is_empty() || exponent.len() > 8 {
387            None
388        } else {
389            let mut bytes = [0_u8; 8];
390            bytes[8 - exponent.len()..].copy_from_slice(exponent);
391            Some(u64::from_be_bytes(bytes))
392        };
393        self.validate_public_metadata(operation, modulus_bits, exponent)
394    }
395
396    pub(crate) fn validate_public_metadata(
397        &self,
398        operation: &'static str,
399        modulus_bits: usize,
400        exponent: Option<u64>,
401    ) -> Result<usize, PolicyViolation> {
402        self.validate()?;
403        if modulus_bits == 0 {
404            return Err(PolicyViolation::InvalidKeyMaterial {
405                operation,
406                key_type: "RSA",
407                reason: "modulus is zero",
408            });
409        }
410        if !(self.minimum_modulus_bits..=crate::hard_limits::RSA_MODULUS_BIT_CEILING)
411            .contains(&modulus_bits)
412        {
413            return Err(PolicyViolation::KeySize {
414                operation,
415                key_type: "RSA",
416                minimum_bits: self.minimum_modulus_bits,
417                maximum_bits: crate::hard_limits::RSA_MODULUS_BIT_CEILING,
418                actual_bits: modulus_bits,
419            });
420        }
421        let exponent = exponent.ok_or(PolicyViolation::InvalidKeyMaterial {
422            operation,
423            key_type: "RSA",
424            reason: "public exponent has invalid encoding",
425        })?;
426        if !(3..=((1_u64 << 33) - 1)).contains(&exponent) || exponent % 2 == 0 {
427            return Err(PolicyViolation::InvalidKeyMaterial {
428                operation,
429                key_type: "RSA",
430                reason: "public exponent is outside the supported odd range",
431            });
432        }
433        Ok(modulus_bits.div_ceil(8))
434    }
435}
436
437#[cfg(any(feature = "xmldsig", feature = "xmlenc"))]
438fn validate_modulus_minimum(
439    resource: &'static str,
440    minimum_bits: usize,
441    alignment_bits: usize,
442    requirement: &'static str,
443    ceiling: usize,
444) -> Result<(), PolicyViolation> {
445    if minimum_bits == 0 || !minimum_bits.is_multiple_of(alignment_bits) {
446        return Err(PolicyViolation::InvalidResourceLimit {
447            resource,
448            requirement,
449            actual: minimum_bits,
450        });
451    }
452    ResourcePolicy::within(resource, minimum_bits, ceiling)
453}
454
455/// Resource ceilings shared by parsing, transforms, and cryptographic output.
456#[derive(Debug, Clone, PartialEq, Eq)]
457pub struct ResourcePolicy {
458    /// Maximum XML nodes in one parsed document.
459    pub max_xml_nodes: usize,
460    /// Maximum element nesting depth in one parsed document.
461    pub max_xml_depth: usize,
462    /// Maximum namespace prefixes simultaneously bound while parsing XML.
463    /// Node and depth limits alone do not bound declarations on one element.
464    pub max_xml_namespace_bindings: usize,
465    /// Maximum references in one signature or manifest.
466    pub max_references: usize,
467    /// Maximum signatures examined and retained in one verification request.
468    pub max_signatures: usize,
469    /// Maximum transforms in one reference.
470    pub max_transforms_per_reference: usize,
471    /// Maximum inherited `xml:base` components in one URI resolution.
472    pub max_xml_base_components: usize,
473    /// Maximum cumulative bytes processed while resolving `xml:base` URIs.
474    pub max_xml_base_resolution_bytes: usize,
475    /// Maximum canonical bytes retained across one signature operation.
476    pub max_canonicalized_bytes: usize,
477    /// Maximum decoded external resource bytes.
478    pub max_external_resource_bytes: usize,
479    /// Maximum bytes in the complete external map and cumulatively dereferenced.
480    pub max_external_resource_total_bytes: usize,
481    /// Maximum XMLEnc plaintext bytes.
482    pub max_encryption_plaintext_bytes: usize,
483    /// Maximum caller-owned XML bytes accepted by any document operation.
484    pub max_xml_document_bytes: usize,
485    /// Maximum cumulative XML bytes parsed by one operation.
486    pub max_xml_parse_work_bytes: usize,
487    /// Maximum independently wrapped recipients.
488    pub max_encryption_recipients: usize,
489    /// Maximum caller-controlled XMLEnc metadata bytes per field.
490    pub max_encryption_metadata_bytes: usize,
491    /// Maximum key-source expansion work and concrete key or certificate
492    /// candidates inspected by one operation stage.
493    pub max_key_candidates: usize,
494    /// Maximum aggregate PBKDF2/PKCS#12 hash rounds or conservative scrypt work
495    /// during key import, including PKCS#8 password-hashing work units.
496    pub max_key_import_kdf_work: usize,
497    /// Maximum estimated scrypt or PKCS#12 KDF workspace bytes during key import.
498    pub max_key_import_kdf_memory_bytes: usize,
499    /// Maximum nested `KeyInfoReference` dereference depth.
500    pub max_key_info_reference_depth: usize,
501    /// Maximum bytes accepted by Base64 transforms before decoding.
502    pub max_base64_transform_input_bytes: usize,
503    /// Maximum cumulative bytes emitted by Base64 transforms in one operation.
504    pub max_base64_transform_output_bytes: usize,
505    /// Maximum XPath expressions evaluated by one signature operation.
506    pub max_xpath_expressions: usize,
507    /// Maximum UTF-8 bytes in one XPath expression.
508    pub max_xpath_expression_bytes: usize,
509    /// Maximum structural complexity accepted for one XPath expression.
510    pub max_xpath_expression_complexity: usize,
511    /// Maximum context-node evaluations for one ordinary XPath transform.
512    pub max_xpath_context_evaluations: usize,
513    /// Maximum conservative XPath node-evaluation work per operation.
514    pub max_xpath_evaluation_work: usize,
515    /// Maximum source strings copied into the XPath mirror.
516    pub max_xpath_mirror_string_bytes: usize,
517    /// Maximum conservative XPath string-processing work per operation.
518    pub max_xpath_string_work_bytes: usize,
519    /// Maximum namespace bindings captured by one XPath expression.
520    pub max_xpath_namespace_bindings: usize,
521    /// Maximum aggregate namespace prefix and URI bytes per XPath expression.
522    pub max_xpath_namespace_bytes: usize,
523    /// Maximum filters in one XPath Filter 2.0 transform.
524    pub max_xpath_filters: usize,
525    /// Maximum cumulative node-set entries visited by filtering transforms.
526    pub max_node_set_filter_work: usize,
527    /// Maximum entries materialized in one exact node set. Defaults to 262,144;
528    /// callers may raise it to the absolute ceiling of 524,288.
529    pub max_node_set_entries: usize,
530    /// Maximum owned string bytes in one materialized node set.
531    pub max_node_set_owned_string_bytes: usize,
532    /// Maximum cumulative owned node-set string bytes per operation.
533    pub max_node_set_cumulative_owned_string_bytes: usize,
534    /// Maximum cumulative OPC parameter allocations retained during signature parsing.
535    pub max_opc_parameter_bytes: usize,
536    /// Maximum cumulative OPC normalization workspace allocations during execution.
537    pub max_opc_workspace_bytes: usize,
538}
539
540impl Default for ResourcePolicy {
541    fn default() -> Self {
542        Self {
543            max_xml_nodes: crate::hard_limits::XML_DOCUMENT_NODE_CEILING as usize,
544            max_xml_depth: crate::hard_limits::XML_DOCUMENT_DEPTH_CEILING,
545            max_xml_namespace_bindings: crate::hard_limits::XML_NAMESPACE_BINDING_CEILING,
546            max_references: crate::hard_limits::SIGNATURE_REFERENCE_CEILING,
547            max_signatures: crate::hard_limits::VERIFICATION_SIGNATURE_CEILING,
548            max_transforms_per_reference: crate::hard_limits::REFERENCE_TRANSFORM_CEILING,
549            max_xml_base_components: crate::hard_limits::XML_BASE_COMPONENT_CEILING,
550            max_xml_base_resolution_bytes: crate::hard_limits::XML_BASE_RESOLUTION_BYTE_CEILING,
551            max_canonicalized_bytes: crate::hard_limits::CANONICALIZED_SIGNATURE_DATA_BYTE_CEILING,
552            max_external_resource_bytes: crate::hard_limits::EXTERNAL_RESOURCE_BYTE_CEILING,
553            max_external_resource_total_bytes:
554                crate::hard_limits::EXTERNAL_RESOURCE_TOTAL_BYTE_CEILING,
555            max_encryption_plaintext_bytes: crate::hard_limits::ENCRYPTION_PLAINTEXT_BYTE_CEILING,
556            max_xml_document_bytes: crate::hard_limits::XML_DOCUMENT_BYTE_CEILING,
557            max_xml_parse_work_bytes: crate::hard_limits::XML_PARSE_WORK_BYTE_CEILING,
558            max_encryption_recipients: crate::hard_limits::ENCRYPTION_RECIPIENT_CEILING,
559            max_encryption_metadata_bytes: crate::hard_limits::ENCRYPTION_METADATA_BYTE_CEILING,
560            max_key_candidates: crate::hard_limits::KEY_CANDIDATE_CEILING,
561            max_key_import_kdf_work: crate::hard_limits::KEY_IMPORT_KDF_WORK_CEILING as usize,
562            max_key_import_kdf_memory_bytes: crate::hard_limits::KEY_IMPORT_KDF_MEMORY_CEILING,
563            max_key_info_reference_depth: crate::hard_limits::KEY_INFO_REFERENCE_DEPTH_CEILING,
564            max_base64_transform_input_bytes:
565                crate::hard_limits::BASE64_TRANSFORM_INPUT_BYTE_CEILING,
566            max_base64_transform_output_bytes:
567                crate::hard_limits::BASE64_TRANSFORM_OUTPUT_BYTE_CEILING,
568            max_xpath_expressions: crate::hard_limits::XPATH_EXPRESSION_COUNT_CEILING,
569            max_xpath_expression_bytes: crate::hard_limits::XPATH_EXPRESSION_BYTE_CEILING,
570            max_xpath_expression_complexity:
571                crate::hard_limits::XPATH_EXPRESSION_COMPLEXITY_CEILING,
572            max_xpath_context_evaluations: crate::hard_limits::XPATH_CONTEXT_EVALUATION_CEILING,
573            max_xpath_evaluation_work: crate::hard_limits::XPATH_EVALUATION_WORK_CEILING,
574            max_xpath_mirror_string_bytes: crate::hard_limits::XPATH_MIRROR_STRING_BYTE_CEILING,
575            max_xpath_string_work_bytes: crate::hard_limits::XPATH_STRING_WORK_BYTE_CEILING,
576            max_xpath_namespace_bindings: crate::hard_limits::XPATH_NAMESPACE_BINDING_CEILING,
577            max_xpath_namespace_bytes: crate::hard_limits::XPATH_NAMESPACE_BYTE_CEILING,
578            max_xpath_filters: crate::hard_limits::XPATH_FILTER_COUNT_CEILING,
579            max_node_set_filter_work: crate::hard_limits::NODE_SET_FILTER_WORK_CEILING,
580            max_node_set_entries: 262_144,
581            max_node_set_owned_string_bytes: crate::hard_limits::NODE_SET_OWNED_STRING_BYTE_CEILING,
582            max_node_set_cumulative_owned_string_bytes:
583                crate::hard_limits::NODE_SET_CUMULATIVE_OWNED_STRING_BYTE_CEILING,
584            max_opc_workspace_bytes: crate::hard_limits::OPC_WORKSPACE_BYTE_CEILING,
585            max_opc_parameter_bytes: crate::hard_limits::OPC_PARAMETER_BYTE_CEILING,
586        }
587    }
588}
589
590impl ResourcePolicy {
591    /// Validate policy values against non-configurable implementation ceilings.
592    pub fn validate(&self) -> Result<(), PolicyViolation> {
593        Self::within(
594            resource_name::XML_NODES,
595            self.max_xml_nodes,
596            crate::hard_limits::XML_DOCUMENT_NODE_CEILING as usize,
597        )?;
598        Self::within(
599            resource_name::XML_DEPTH,
600            self.max_xml_depth,
601            crate::hard_limits::XML_DOCUMENT_DEPTH_CEILING,
602        )?;
603        Self::within(
604            resource_name::XML_NAMESPACE_BINDINGS,
605            self.max_xml_namespace_bindings,
606            crate::hard_limits::XML_NAMESPACE_BINDING_CEILING,
607        )?;
608        Self::within(
609            resource_name::CANONICALIZED_BYTES,
610            self.max_canonicalized_bytes,
611            crate::hard_limits::CANONICALIZED_SIGNATURE_DATA_BYTE_CEILING,
612        )?;
613        Self::within(
614            resource_name::SIGNATURE_REFERENCES,
615            self.max_references,
616            crate::hard_limits::SIGNATURE_REFERENCE_CEILING,
617        )?;
618        Self::within(
619            resource_name::VERIFICATION_SIGNATURES,
620            self.max_signatures,
621            crate::hard_limits::VERIFICATION_SIGNATURE_CEILING,
622        )?;
623        Self::within(
624            resource_name::REFERENCE_TRANSFORMS,
625            self.max_transforms_per_reference,
626            crate::hard_limits::REFERENCE_TRANSFORM_CEILING,
627        )?;
628        Self::within(
629            resource_name::XML_BASE_COMPONENTS,
630            self.max_xml_base_components,
631            crate::hard_limits::XML_BASE_COMPONENT_CEILING,
632        )?;
633        Self::within(
634            resource_name::XML_BASE_RESOLUTION_BYTES,
635            self.max_xml_base_resolution_bytes,
636            crate::hard_limits::XML_BASE_RESOLUTION_BYTE_CEILING,
637        )?;
638        Self::within(
639            resource_name::XML_DOCUMENT,
640            self.max_xml_document_bytes,
641            crate::hard_limits::XML_DOCUMENT_BYTE_CEILING,
642        )?;
643        Self::within(
644            resource_name::XML_PARSE_WORK_BYTES,
645            self.max_xml_parse_work_bytes,
646            crate::hard_limits::XML_PARSE_WORK_BYTE_CEILING,
647        )?;
648        Self::within(
649            resource_name::EXTERNAL_RESOURCE_BYTES,
650            self.max_external_resource_bytes,
651            crate::hard_limits::EXTERNAL_RESOURCE_BYTE_CEILING,
652        )?;
653        Self::within(
654            resource_name::AGGREGATE_EXTERNAL_RESOURCE_BYTES,
655            self.max_external_resource_total_bytes,
656            crate::hard_limits::EXTERNAL_RESOURCE_TOTAL_BYTE_CEILING,
657        )?;
658        Self::within(
659            resource_name::ENCRYPTION_PLAINTEXT_BYTES,
660            self.max_encryption_plaintext_bytes,
661            crate::hard_limits::ENCRYPTION_PLAINTEXT_BYTE_CEILING,
662        )?;
663        Self::within(
664            resource_name::ENCRYPTION_RECIPIENTS,
665            self.max_encryption_recipients,
666            crate::hard_limits::ENCRYPTION_RECIPIENT_CEILING,
667        )?;
668        Self::within(
669            resource_name::ENCRYPTION_METADATA_BYTES,
670            self.max_encryption_metadata_bytes,
671            crate::hard_limits::ENCRYPTION_METADATA_BYTE_CEILING,
672        )?;
673        for (resource, selected, ceiling) in [
674            (
675                resource_name::KEY_CANDIDATES,
676                self.max_key_candidates,
677                crate::hard_limits::KEY_CANDIDATE_CEILING,
678            ),
679            (
680                resource_name::KEY_IMPORT_KDF_WORK,
681                self.max_key_import_kdf_work,
682                crate::hard_limits::KEY_IMPORT_KDF_WORK_CEILING as usize,
683            ),
684            (
685                resource_name::KEY_IMPORT_KDF_MEMORY,
686                self.max_key_import_kdf_memory_bytes,
687                crate::hard_limits::KEY_IMPORT_KDF_MEMORY_CEILING,
688            ),
689            (
690                resource_name::KEY_INFO_REFERENCE_DEPTH,
691                self.max_key_info_reference_depth,
692                crate::hard_limits::KEY_INFO_REFERENCE_DEPTH_CEILING,
693            ),
694            (
695                resource_name::BASE64_TRANSFORM_INPUT_BYTES,
696                self.max_base64_transform_input_bytes,
697                crate::hard_limits::BASE64_TRANSFORM_INPUT_BYTE_CEILING,
698            ),
699            (
700                resource_name::BASE64_TRANSFORM_OUTPUT_BYTES,
701                self.max_base64_transform_output_bytes,
702                crate::hard_limits::BASE64_TRANSFORM_OUTPUT_BYTE_CEILING,
703            ),
704            (
705                resource_name::XPATH_EXPRESSIONS,
706                self.max_xpath_expressions,
707                crate::hard_limits::XPATH_EXPRESSION_COUNT_CEILING,
708            ),
709            (
710                resource_name::XPATH_EXPRESSION_BYTES,
711                self.max_xpath_expression_bytes,
712                crate::hard_limits::XPATH_EXPRESSION_BYTE_CEILING,
713            ),
714            (
715                resource_name::XPATH_EXPRESSION_COMPLEXITY,
716                self.max_xpath_expression_complexity,
717                crate::hard_limits::XPATH_EXPRESSION_COMPLEXITY_CEILING,
718            ),
719            (
720                resource_name::XPATH_CONTEXT_EVALUATIONS,
721                self.max_xpath_context_evaluations,
722                crate::hard_limits::XPATH_CONTEXT_EVALUATION_CEILING,
723            ),
724            (
725                resource_name::XPATH_EVALUATION_WORK,
726                self.max_xpath_evaluation_work,
727                crate::hard_limits::XPATH_EVALUATION_WORK_CEILING,
728            ),
729            (
730                resource_name::XPATH_MIRROR_STRING_BYTES,
731                self.max_xpath_mirror_string_bytes,
732                crate::hard_limits::XPATH_MIRROR_STRING_BYTE_CEILING,
733            ),
734            (
735                resource_name::XPATH_STRING_WORK_BYTES,
736                self.max_xpath_string_work_bytes,
737                crate::hard_limits::XPATH_STRING_WORK_BYTE_CEILING,
738            ),
739            (
740                resource_name::XPATH_NAMESPACE_BINDINGS,
741                self.max_xpath_namespace_bindings,
742                crate::hard_limits::XPATH_NAMESPACE_BINDING_CEILING,
743            ),
744            (
745                resource_name::XPATH_NAMESPACE_BYTES,
746                self.max_xpath_namespace_bytes,
747                crate::hard_limits::XPATH_NAMESPACE_BYTE_CEILING,
748            ),
749            (
750                resource_name::XPATH_FILTERS,
751                self.max_xpath_filters,
752                crate::hard_limits::XPATH_FILTER_COUNT_CEILING,
753            ),
754            (
755                resource_name::NODE_SET_FILTER_WORK,
756                self.max_node_set_filter_work,
757                crate::hard_limits::NODE_SET_FILTER_WORK_CEILING,
758            ),
759            (
760                resource_name::NODE_SET_ENTRIES,
761                self.max_node_set_entries,
762                crate::hard_limits::NODE_SET_ENTRY_CEILING,
763            ),
764            (
765                resource_name::NODE_SET_OWNED_STRING_BYTES,
766                self.max_node_set_owned_string_bytes,
767                crate::hard_limits::NODE_SET_OWNED_STRING_BYTE_CEILING,
768            ),
769            (
770                resource_name::NODE_SET_CUMULATIVE_OWNED_STRING_BYTES,
771                self.max_node_set_cumulative_owned_string_bytes,
772                crate::hard_limits::NODE_SET_CUMULATIVE_OWNED_STRING_BYTE_CEILING,
773            ),
774        ] {
775            Self::within(resource, selected, ceiling)?;
776        }
777        Self::within(
778            resource_name::OPC_WORKSPACE_BYTES,
779            self.max_opc_workspace_bytes,
780            crate::hard_limits::OPC_WORKSPACE_BYTE_CEILING,
781        )?;
782        Self::within(
783            resource_name::OPC_PARAMETER_BYTES,
784            self.max_opc_parameter_bytes,
785            crate::hard_limits::OPC_PARAMETER_BYTE_CEILING,
786        )?;
787        Ok(())
788    }
789
790    pub(crate) fn validate_xml_document_len(&self, actual: usize) -> Result<(), PolicyViolation> {
791        if actual > self.max_xml_document_bytes {
792            return Err(PolicyViolation::ResourceLimit {
793                resource: resource_name::XML_DOCUMENT,
794                maximum: self.max_xml_document_bytes,
795                actual,
796            });
797        }
798        Ok(())
799    }
800
801    #[cfg(any(feature = "xmldsig", feature = "xmlenc"))]
802    /// Reject aggregate key-candidate work beyond this policy snapshot.
803    pub fn validate_key_candidates(&self, actual: usize) -> Result<(), PolicyViolation> {
804        if actual > self.max_key_candidates {
805            return Err(PolicyViolation::ResourceLimit {
806                resource: resource_name::KEY_CANDIDATES,
807                maximum: self.max_key_candidates,
808                actual,
809            });
810        }
811        Ok(())
812    }
813
814    /// Reject `KeyInfoReference` traversal beyond this policy snapshot.
815    #[cfg(feature = "xmldsig")]
816    pub fn validate_key_info_reference_depth(&self, actual: usize) -> Result<(), PolicyViolation> {
817        if actual > self.max_key_info_reference_depth {
818            return Err(PolicyViolation::ResourceLimit {
819                resource: resource_name::KEY_INFO_REFERENCE_DEPTH,
820                maximum: self.max_key_info_reference_depth,
821                actual,
822            });
823        }
824        Ok(())
825    }
826
827    pub(crate) fn effective_xml_nodes(&self) -> u32 {
828        u32::try_from(self.max_xml_nodes)
829            .unwrap_or(crate::hard_limits::XML_DOCUMENT_NODE_CEILING)
830            .min(crate::hard_limits::XML_DOCUMENT_NODE_CEILING)
831    }
832
833    #[cfg(feature = "xmldsig")]
834    pub(crate) fn effective_canonicalized_bytes(&self) -> usize {
835        self.max_canonicalized_bytes
836            .min(crate::hard_limits::CANONICALIZED_SIGNATURE_DATA_BYTE_CEILING)
837    }
838
839    #[cfg(feature = "xmldsig")]
840    pub(crate) fn effective_xml_base_components(&self) -> usize {
841        self.max_xml_base_components
842            .min(crate::hard_limits::XML_BASE_COMPONENT_CEILING)
843    }
844
845    #[cfg(feature = "xmldsig")]
846    pub(crate) fn effective_xml_base_resolution_bytes(&self) -> usize {
847        self.max_xml_base_resolution_bytes
848            .min(crate::hard_limits::XML_BASE_RESOLUTION_BYTE_CEILING)
849    }
850
851    fn within(
852        resource: &'static str,
853        selected: usize,
854        ceiling: usize,
855    ) -> Result<(), PolicyViolation> {
856        if selected > ceiling {
857            return Err(PolicyViolation::ResourceLimit {
858                resource,
859                maximum: ceiling,
860                actual: selected,
861            });
862        }
863        Ok(())
864    }
865
866    #[cfg(feature = "xmldsig")]
867    fn nonzero_within(
868        resource: &'static str,
869        selected: usize,
870        ceiling: usize,
871    ) -> Result<(), PolicyViolation> {
872        if selected == 0 {
873            return Err(PolicyViolation::InvalidResourceLimit {
874                resource,
875                requirement: "limit must be nonzero",
876                actual: selected,
877            });
878        }
879        Self::within(resource, selected, ceiling)
880    }
881}
882
883/// XML parsing decisions shared by all operation policies.
884#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
885pub struct XmlInputPolicy {
886    /// Permit bounded internal DTD declarations. External resolution stays off.
887    pub allow_internal_dtd: bool,
888}
889
890/// Trusted edition of relationship selection and normalization.
891/// This is not an OPC package-conformance profile: signature placement and
892/// package-wide part identity constraints require a package validator.
893#[cfg(feature = "xmldsig")]
894#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
895pub enum OpcRelationshipEdition {
896    /// ECMA-376 Part 2 (2012), including libxmlsec1's case-sensitive selection.
897    Ecma2012,
898    /// ECMA-376 Part 2 (2021) section 10.6, ASCII-insensitive selection.
899    #[default]
900    Ecma2021,
901}
902
903/// XMLDSig transform and canonicalization decisions shared by signing and verification.
904#[cfg(feature = "xmldsig")]
905#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
906pub enum SameDocumentIdSemantics {
907    /// Require bare fragment identifiers to satisfy the XML NCName grammar.
908    #[default]
909    Specification,
910    /// Apply libxmlsec1's default barename compatibility grammar.
911    ///
912    /// Registered non-NCName values are accepted unless a single quote makes
913    /// them unrepresentable in the donor's single-quoted XPointer expression.
914    /// The resulting node set retains barename semantics and excludes comments.
915    XmlSecBarename,
916    /// Resolve the fragment text directly as an ID, including non-NCName values.
917    ///
918    /// This reproduces libxmlsec1's explicit Visa3D compatibility flag.
919    XmlSecVisa3d,
920}
921
922/// Wire representation used for ECDSA `SignatureValue` bytes.
923#[cfg(feature = "xmldsig")]
924#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
925pub enum EcdsaSignatureValueEncoding {
926    /// XMLDSig fixed-width `r || s` representation.
927    #[default]
928    XmlDsig,
929    /// ASN.1 DER `SEQUENCE(INTEGER(r), INTEGER(s))` compatibility representation.
930    XmlSecAsn1Der,
931}
932
933/// XMLDSig transform and canonicalization decisions shared by signing and verification.
934#[cfg(feature = "xmldsig")]
935#[derive(Debug, Clone, Default, PartialEq, Eq)]
936pub struct TransformPolicy {
937    /// Allowed transform and canonicalization URIs; `None` accepts every implemented algorithm.
938    pub allowed_algorithms: Option<HashSet<String>>,
939    /// Node selected for the XPath `here()` extension function.
940    pub xpath_here_semantics: XPathHereSemantics,
941    /// Interpretation of bare same-document ID fragments.
942    pub same_document_id_semantics: SameDocumentIdSemantics,
943    /// Trusted normalization edition, not package validation; never inferred from XML.
944    pub opc_relationship_edition: OpcRelationshipEdition,
945}
946
947/// URI-class decisions shared by XMLDSig reference and key retrieval processing.
948#[cfg(feature = "xmldsig")]
949#[derive(Debug, Clone, Copy, PartialEq, Eq)]
950pub struct UriPolicy {
951    /// URI classes accepted by SignedInfo and Manifest references.
952    pub references: UriTypeSet,
953    /// URI classes accepted by RetrievalMethod processing.
954    pub retrieval_methods: UriTypeSet,
955    /// URI classes accepted by XMLDSig 1.1 `KeyInfoReference` processing.
956    pub key_info_references: UriTypeSet,
957}
958
959#[cfg(feature = "xmldsig")]
960impl Default for UriPolicy {
961    fn default() -> Self {
962        Self {
963            references: UriTypeSet::SAME_DOCUMENT,
964            retrieval_methods: UriTypeSet::SAME_DOCUMENT,
965            key_info_references: UriTypeSet::SAME_DOCUMENT,
966        }
967    }
968}
969
970/// KeyInfo sources an XMLDSig verification operation is permitted to trust.
971#[cfg(feature = "xmldsig")]
972#[derive(Debug, Clone, Copy, PartialEq, Eq)]
973pub struct KeySourcePolicy {
974    /// Permit a caller-supplied pre-resolved key.
975    pub preset_key: bool,
976    /// Permit keys selected by document `KeyName`.
977    pub key_name: bool,
978    /// Permit public keys embedded in `KeyValue`.
979    pub key_value: bool,
980    /// Permit public keys embedded in `DEREncodedKeyValue`.
981    pub der_encoded_key_value: bool,
982    /// Permit certificates and selectors embedded in `X509Data`.
983    pub x509_data: bool,
984    /// Permit `KeyInfoReference` indirection to another bounded `KeyInfo`.
985    pub key_info_reference: bool,
986}
987
988#[cfg(feature = "xmldsig")]
989impl Default for KeySourcePolicy {
990    fn default() -> Self {
991        Self {
992            preset_key: true,
993            key_name: true,
994            key_value: true,
995            der_encoded_key_value: true,
996            x509_data: true,
997            key_info_reference: true,
998        }
999    }
1000}
1001
1002/// An RFC 5280 extended-key-purpose identifier accepted for XML signing.
1003#[cfg(feature = "xmldsig")]
1004#[derive(Debug, Clone, PartialEq, Eq, Hash)]
1005#[non_exhaustive]
1006pub enum ExtendedKeyPurpose {
1007    /// TLS server authentication (`id-kp-serverAuth`).
1008    ServerAuth,
1009    /// TLS client authentication (`id-kp-clientAuth`).
1010    ClientAuth,
1011    /// Executable code signing (`id-kp-codeSigning`).
1012    CodeSigning,
1013    /// Email protection (`id-kp-emailProtection`).
1014    EmailProtection,
1015    /// Trusted timestamping (`id-kp-timeStamping`).
1016    TimeStamping,
1017    /// OCSP response signing (`id-kp-OCSPSigning`).
1018    OcspSigning,
1019    /// Application-defined purpose represented as OID arcs.
1020    Other(Vec<u64>),
1021}
1022
1023/// Whether verification must establish signer authorization in addition to mathematics.
1024#[cfg(feature = "xmldsig")]
1025#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
1026pub enum VerificationTrustMode {
1027    /// Require path validation or an explicit caller trust declaration.
1028    #[default]
1029    RequireTrustedKey,
1030    /// Check mathematical validity without authenticating the signer.
1031    CryptographicOnly,
1032}
1033
1034/// X.509 and key-resolution decisions for verification.
1035#[cfg(feature = "xmldsig")]
1036#[derive(Debug, Clone, PartialEq, Eq)]
1037pub struct KeyTrustPolicy {
1038    /// Whether mathematical validity alone is sufficient for the operation.
1039    pub mode: VerificationTrustMode,
1040    /// Require embedded or selected certificates to chain to a configured anchor.
1041    pub verify_x509_chains: bool,
1042    /// Maximum validated path depth.
1043    pub max_x509_chain_depth: usize,
1044    /// Maximum complete or partial signature-valid path states generated.
1045    pub max_x509_candidate_paths: usize,
1046    /// Legacy signature algorithms explicitly permitted for verification.
1047    pub allowed_legacy_signature_algorithms: HashSet<SignatureAlgorithm>,
1048    /// Independent legacy-hash permission for parameterized RSA-PSS verification.
1049    pub parameterized_rsa_pss: RsaPssPermission,
1050    /// Certificate and CRL signature permissions, independent of XML signatures.
1051    /// `None` permits supported classical algorithms but rejects PQ algorithms;
1052    /// Explicit permissions select either an exact allowlist (including PSS
1053    /// parameters) or all provider-supported algorithms for compatibility.
1054    pub certificate_signature_algorithms: Option<CertificateSignatureAlgorithms>,
1055    /// RSA requirements enforced for resolved verification keys and issuer keys.
1056    pub rsa_keys: RsaKeyPolicy,
1057    /// DSA requirements enforced for resolved verification keys.
1058    pub dsa_keys: DsaKeyPolicy,
1059    /// Purposes accepted when any certificate in a path carries ExtendedKeyUsage.
1060    ///
1061    /// An empty set accepts only paths whose certificates omit ExtendedKeyUsage
1062    /// or use `anyExtendedKeyUsage`; it does not treat TLS/code-signing purposes
1063    /// as a generic authorization for XML signatures.
1064    pub allowed_extended_key_usages: HashSet<ExtendedKeyPurpose>,
1065    /// Authenticate and enforce embedded CRLs during path validation.
1066    /// Requires [`Self::verify_x509_chains`].
1067    pub check_crls: bool,
1068    /// Verification time override; `None` selects the system clock.
1069    pub verification_time: Option<SystemTime>,
1070}
1071
1072#[cfg(feature = "xmldsig")]
1073impl Default for KeyTrustPolicy {
1074    fn default() -> Self {
1075        Self {
1076            mode: VerificationTrustMode::RequireTrustedKey,
1077            verify_x509_chains: false,
1078            max_x509_chain_depth: crate::hard_limits::X509_CHAIN_DEPTH_CEILING,
1079            max_x509_candidate_paths: crate::hard_limits::X509_CANDIDATE_PATH_CEILING,
1080            allowed_legacy_signature_algorithms: HashSet::new(),
1081            parameterized_rsa_pss: RsaPssPermission::ExactOnly,
1082            certificate_signature_algorithms: None,
1083            rsa_keys: RsaKeyPolicy::default(),
1084            dsa_keys: DsaKeyPolicy::default(),
1085            allowed_extended_key_usages: HashSet::new(),
1086            check_crls: false,
1087            verification_time: None,
1088        }
1089    }
1090}
1091
1092#[cfg(feature = "xmldsig")]
1093#[derive(Debug, Clone, PartialEq, Eq)]
1094/// Explicit certificate/CRL permissions, independent of XML signature methods.
1095pub enum CertificateSignatureAlgorithms {
1096    /// Permit any algorithm the selected provider implements, including PQ.
1097    /// This is an explicit compatibility opt-in, not a provider fallback.
1098    AllSupported,
1099    /// Permit only exact algorithms, including every RSA-PSS parameter.
1100    Allowlist(HashSet<crate::provider::X509SignatureAlgorithm>),
1101}
1102
1103#[cfg(feature = "xmldsig")]
1104impl KeyTrustPolicy {
1105    pub(crate) fn check_certificate_signature_algorithm(
1106        algorithm: crate::provider::X509SignatureAlgorithm,
1107        allowed: Option<&CertificateSignatureAlgorithms>,
1108    ) -> Result<(), PolicyViolation> {
1109        // This is deployment permission, not a PKIX syntax requirement.
1110        // Algorithm parameters are validated before this gate; compiled
1111        // provider capability is checked only after permission is established.
1112        let permitted = match allowed {
1113            Some(CertificateSignatureAlgorithms::AllSupported) => true,
1114            Some(CertificateSignatureAlgorithms::Allowlist(allowed)) => {
1115                allowed.contains(&algorithm)
1116            }
1117            None => !matches!(
1118                algorithm,
1119                crate::provider::X509SignatureAlgorithm::PostQuantum(_)
1120            ),
1121        };
1122        if !permitted {
1123            return Err(PolicyViolation::Algorithm {
1124                operation: "certificate/CRL verification",
1125                algorithm: algorithm
1126                    .oid()
1127                    .map_or_else(|| format!("{algorithm:?}"), str::to_owned),
1128            });
1129        }
1130        Ok(())
1131    }
1132
1133    pub(crate) fn validate(&self) -> Result<(), PolicyViolation> {
1134        if self.check_crls && !self.verify_x509_chains {
1135            return Err(PolicyViolation::KeyTrust {
1136                reason: "CRL checking requires X.509 chain validation",
1137            });
1138        }
1139        if self
1140            .allowed_extended_key_usages
1141            .iter()
1142            .any(|purpose| match purpose {
1143                ExtendedKeyPurpose::Other(arcs) => {
1144                    arcs.len() < 2 || arcs[0] > 2 || (arcs[0] < 2 && arcs[1] > 39)
1145                }
1146                _ => false,
1147            })
1148        {
1149            return Err(PolicyViolation::KeyTrust {
1150                reason: "custom extended key purposes must contain valid OID arcs",
1151            });
1152        }
1153        self.rsa_keys.validate()?;
1154        self.dsa_keys.validate()?;
1155        ResourcePolicy::nonzero_within(
1156            "X.509 chain depth",
1157            self.max_x509_chain_depth,
1158            crate::hard_limits::X509_CHAIN_DEPTH_CEILING,
1159        )?;
1160        ResourcePolicy::nonzero_within(
1161            "X.509 candidate paths",
1162            self.max_x509_candidate_paths,
1163            crate::hard_limits::X509_CANDIDATE_PATH_CEILING,
1164        )
1165    }
1166}
1167
1168/// Whether an XMLDSig operation evaluates direct `<Object>/<Manifest>` references.
1169#[cfg(feature = "xmldsig")]
1170#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
1171pub enum ManifestProcessing {
1172    /// Leave Manifest reference values untouched and perform no Manifest work.
1173    #[default]
1174    Ignore,
1175    /// Evaluate Manifest references under the operation's shared resource policy.
1176    Process,
1177}
1178
1179/// Immutable policy snapshot for XMLDSig verification.
1180#[cfg(feature = "xmldsig")]
1181#[derive(Debug, Clone, Default)]
1182pub struct VerificationPolicy {
1183    /// Shared key-establishment permissions and cumulative operation limits.
1184    pub key_establishment: KeyEstablishmentPolicy,
1185    /// Family permission selected by the caller, never derived from document input.
1186    /// Legacy hashes additionally require the independent key-trust permission.
1187    pub parameterized_rsa_pss: RsaPssPermission,
1188    /// Exact signature methods, supplemented by `parameterized_rsa_pss`.
1189    /// `None` accepts every implemented method subject to
1190    /// independent gates such as [`KeyTrustPolicy::allowed_legacy_signature_algorithms`].
1191    pub signature_algorithms: Option<HashSet<SignatureAlgorithm>>,
1192    /// Allowed reference digest methods; `None` accepts every implemented method.
1193    pub digest_algorithms: Option<HashSet<DigestAlgorithm>>,
1194    /// HMAC secret and output-length requirements.
1195    pub hmac: HmacPolicy,
1196    /// Required ECDSA `SignatureValue` wire representation.
1197    pub ecdsa_signature_value_encoding: EcdsaSignatureValueEncoding,
1198    /// Key and certificate trust rules.
1199    pub key_trust: KeyTrustPolicy,
1200    /// KeyInfo source permissions.
1201    pub key_sources: KeySourcePolicy,
1202    /// Reference and key-retrieval URI permissions.
1203    pub uris: UriPolicy,
1204    /// Transform and canonicalization permissions.
1205    pub transforms: TransformPolicy,
1206    /// Whether authenticated Manifest references are processed.
1207    pub manifest_processing: ManifestProcessing,
1208    /// XML parser rules.
1209    pub xml: XmlInputPolicy,
1210    /// Resource ceilings.
1211    pub resources: ResourcePolicy,
1212}
1213
1214#[cfg(feature = "xmldsig")]
1215impl VerificationPolicy {
1216    pub(crate) fn check_digest_algorithm(
1217        &self,
1218        algorithm: DigestAlgorithm,
1219    ) -> Result<(), PolicyViolation> {
1220        if self
1221            .digest_algorithms
1222            .as_ref()
1223            .map_or(!algorithm.requires_explicit_permission(), |allowed| {
1224                allowed.contains(&algorithm)
1225            })
1226        {
1227            Ok(())
1228        } else {
1229            Err(PolicyViolation::Algorithm {
1230                operation: "verification",
1231                algorithm: algorithm.uri().to_owned(),
1232            })
1233        }
1234    }
1235    /// Validate the complete snapshot against implementation hard ceilings.
1236    pub fn validate(&self) -> Result<(), PolicyViolation> {
1237        self.resources.validate()?;
1238        self.key_establishment.validate()?;
1239        self.key_trust.validate()?;
1240        self.hmac.validate()
1241    }
1242
1243    /// Enforce the signature algorithm after key resolution.
1244    pub fn check_signature_algorithm(
1245        &self,
1246        algorithm: SignatureAlgorithm,
1247    ) -> Result<(), PolicyViolation> {
1248        if (matches!(algorithm, SignatureAlgorithm::PostQuantum(_))
1249            || algorithm.requires_explicit_permission())
1250            && !self
1251                .signature_algorithms
1252                .as_ref()
1253                .is_some_and(|allowed| allowed.contains(&algorithm))
1254        {
1255            return Err(PolicyViolation::Algorithm {
1256                operation: "verification",
1257                algorithm: algorithm.uri().to_owned(),
1258            });
1259        }
1260        if (algorithm.uses_legacy_pss_hash()
1261            || matches!(
1262                algorithm,
1263                SignatureAlgorithm::RsaSha1
1264                    | SignatureAlgorithm::DsaSha1
1265                    | SignatureAlgorithm::HmacSha1
1266                    | SignatureAlgorithm::EcdsaSha1
1267            ))
1268            && !self
1269                .key_trust
1270                .allowed_legacy_signature_algorithms
1271                .contains(&algorithm)
1272            && !self.key_trust.parameterized_rsa_pss.permits(algorithm)
1273        {
1274            return Err(PolicyViolation::Algorithm {
1275                operation: "verification",
1276                algorithm: algorithm.uri().to_string(),
1277            });
1278        }
1279        if self
1280            .signature_algorithms
1281            .as_ref()
1282            .is_some_and(|allowed| !allowed.contains(&algorithm))
1283            && !self.parameterized_rsa_pss.permits(algorithm)
1284        {
1285            return Err(PolicyViolation::Algorithm {
1286                operation: "verification",
1287                algorithm: algorithm.uri().to_string(),
1288            });
1289        }
1290        Ok(())
1291    }
1292}
1293
1294/// Immutable policy snapshot for XMLDSig signing.
1295#[cfg(feature = "xmldsig")]
1296#[derive(Debug, Clone, Default)]
1297pub struct SigningPolicy {
1298    /// Shared key-establishment permissions and cumulative operation limits.
1299    pub key_establishment: KeyEstablishmentPolicy,
1300    /// Explicit parameterized RSA-PSS family permission, including legacy hashes.
1301    pub parameterized_rsa_pss: RsaPssPermission,
1302    /// Exact signing methods, supplemented by `parameterized_rsa_pss`.
1303    /// `None` uses the implemented secure defaults.
1304    pub signature_algorithms: Option<HashSet<SignatureAlgorithm>>,
1305    /// Allowed reference digest methods; `None` uses the implemented secure defaults.
1306    pub digest_algorithms: Option<HashSet<DigestAlgorithm>>,
1307    /// HMAC secret and output-length requirements.
1308    pub hmac: HmacPolicy,
1309    /// ECDSA `SignatureValue` wire representation emitted by signing.
1310    pub ecdsa_signature_value_encoding: EcdsaSignatureValueEncoding,
1311    /// RSA requirements enforced before producing a signature.
1312    pub rsa_keys: RsaKeyPolicy,
1313    /// DSA requirements enforced before producing a signature.
1314    pub dsa_keys: DsaKeyPolicy,
1315    /// Reference URI permissions. External URIs resolve only from caller-supplied
1316    /// request bytes and never enable filesystem or network access.
1317    pub uris: UriPolicy,
1318    /// Transform and canonicalization permissions.
1319    pub transforms: TransformPolicy,
1320    /// Whether direct `<Object>/<Manifest>` reference digests are populated.
1321    pub manifest_processing: ManifestProcessing,
1322    /// XML parser rules.
1323    pub xml: XmlInputPolicy,
1324    /// Resource ceilings.
1325    pub resources: ResourcePolicy,
1326}
1327
1328#[cfg(feature = "xmldsig")]
1329impl SigningPolicy {
1330    /// Validate the complete snapshot before signing work begins.
1331    pub fn validate(&self) -> Result<(), PolicyViolation> {
1332        self.resources.validate()?;
1333        self.key_establishment.validate()?;
1334        self.rsa_keys.validate()?;
1335        self.dsa_keys.validate()?;
1336        self.hmac.validate()
1337    }
1338
1339    pub(crate) fn check_signature_algorithm(
1340        &self,
1341        algorithm: SignatureAlgorithm,
1342    ) -> Result<(), PolicyViolation> {
1343        if self.parameterized_rsa_pss.permits(algorithm) {
1344            return Ok(());
1345        }
1346        check_signing_algorithm(
1347            self.signature_algorithms.as_ref(),
1348            algorithm,
1349            algorithm.signing_allowed(),
1350            algorithm.uri(),
1351        )
1352    }
1353
1354    pub(crate) fn check_digest_algorithm(
1355        &self,
1356        algorithm: DigestAlgorithm,
1357    ) -> Result<(), PolicyViolation> {
1358        check_signing_algorithm(
1359            self.digest_algorithms.as_ref(),
1360            algorithm,
1361            algorithm.signing_allowed(),
1362            algorithm.uri(),
1363        )
1364    }
1365}
1366
1367#[cfg(feature = "xmldsig")]
1368fn check_signing_algorithm<T: Eq + std::hash::Hash>(
1369    allowlist: Option<&HashSet<T>>,
1370    algorithm: T,
1371    default_allowed: bool,
1372    uri: &str,
1373) -> Result<(), PolicyViolation> {
1374    if allowlist.map_or(default_allowed, |allowed| allowed.contains(&algorithm)) {
1375        Ok(())
1376    } else {
1377        Err(PolicyViolation::Algorithm {
1378            operation: "signing",
1379            algorithm: uri.to_owned(),
1380        })
1381    }
1382}
1383
1384/// Enforce the shared content-cipher permission contract before dispatch.
1385#[cfg(feature = "xmlenc")]
1386pub(crate) fn check_content_algorithm(
1387    allowlist: Option<&HashSet<DataEncryptionAlgorithm>>,
1388    algorithm: DataEncryptionAlgorithm,
1389    operation: &'static str,
1390) -> Result<(), PolicyViolation> {
1391    if allowlist.map_or(!algorithm.requires_explicit_permission(), |allowed| {
1392        allowed.contains(&algorithm)
1393    }) {
1394        Ok(())
1395    } else {
1396        Err(PolicyViolation::Algorithm {
1397            operation,
1398            algorithm: algorithm.uri().to_owned(),
1399        })
1400    }
1401}
1402
1403/// Immutable policy snapshot for XMLEnc encryption.
1404#[cfg(feature = "xmlenc")]
1405#[derive(Debug, Clone, Default)]
1406pub struct EncryptionPolicy {
1407    /// Allowed content-encryption algorithms.
1408    pub data_algorithms: Option<HashSet<DataEncryptionAlgorithm>>,
1409    /// Allowed RSA key-transport algorithms.
1410    pub key_transport_algorithms: Option<HashSet<KeyTransportAlgorithm>>,
1411    /// Allowed symmetric key-wrap algorithms.
1412    pub key_wrap_algorithms: Option<HashSet<KeyWrapAlgorithm>>,
1413    /// Allowed OAEP digest algorithms.
1414    pub oaep_digests: Option<HashSet<OaepDigestAlgorithm>>,
1415    /// RSA requirements enforced when producing OAEP key transport.
1416    pub rsa_keys: RsaKeyPolicy,
1417    /// Shared agreement/KDF permission and cumulative work requirements.
1418    pub key_establishment: KeyEstablishmentPolicy,
1419    /// XML parser rules.
1420    pub xml: XmlInputPolicy,
1421    /// Resource ceilings.
1422    pub resources: ResourcePolicy,
1423}
1424
1425#[cfg(feature = "xmlenc")]
1426impl EncryptionPolicy {
1427    /// Validate the complete snapshot before outbound encryption work begins.
1428    pub fn validate(&self) -> Result<(), PolicyViolation> {
1429        self.resources.validate()?;
1430        self.key_establishment.validate()?;
1431        self.rsa_keys.validate()
1432    }
1433}
1434
1435/// Immutable policy snapshot for XMLEnc decryption.
1436#[cfg(feature = "xmlenc")]
1437#[derive(Debug, Clone, Default)]
1438pub struct DecryptionPolicy {
1439    /// URI-class permissions for ciphertext and indirect key references.
1440    pub uris: UriPolicy,
1441    /// Shared transform permissions and same-document reference semantics.
1442    pub transforms: TransformPolicy,
1443    /// Allowed content-decryption algorithms.
1444    pub data_algorithms: Option<HashSet<DataEncryptionAlgorithm>>,
1445    /// Allowed RSA key-transport algorithms accepted on input.
1446    pub key_transport_algorithms: Option<HashSet<KeyTransportAlgorithm>>,
1447    /// Allowed symmetric key-wrap algorithms accepted on input.
1448    pub key_wrap_algorithms: Option<HashSet<KeyWrapAlgorithm>>,
1449    /// Allowed OAEP digest algorithms accepted on input.
1450    pub oaep_digests: Option<HashSet<OaepDigestAlgorithm>>,
1451    /// RSA requirements enforced before OAEP recovery; defaults to 2048 bits.
1452    /// Legacy input requires an explicit caller-selected lower minimum.
1453    pub rsa_keys: RsaKeyPolicy,
1454    /// Shared agreement/KDF permission and cumulative work requirements.
1455    pub key_establishment: KeyEstablishmentPolicy,
1456    /// XML parser rules.
1457    pub xml: XmlInputPolicy,
1458    /// Resource ceilings.
1459    pub resources: ResourcePolicy,
1460}
1461
1462#[cfg(feature = "xmlenc")]
1463impl DecryptionPolicy {
1464    /// Validate the complete snapshot before inbound decryption work begins.
1465    pub fn validate(&self) -> Result<(), PolicyViolation> {
1466        self.resources.validate()?;
1467        self.key_establishment.validate()?;
1468        self.rsa_keys.validate()
1469    }
1470}
1471
1472#[cfg(test)]
1473mod tests {
1474    use super::*;
1475
1476    #[cfg(all(feature = "legacy-algorithms", feature = "xmldsig"))]
1477    #[test]
1478    fn legacy_digest_permission_is_explicit_and_operation_local() {
1479        // Capability does not imply acceptance, and permission for one digest
1480        // or operation must never enable another digest or operation.
1481        for algorithm in [DigestAlgorithm::Md5, DigestAlgorithm::Ripemd160] {
1482            assert!(
1483                VerificationPolicy::default()
1484                    .check_digest_algorithm(algorithm)
1485                    .is_err()
1486            );
1487            assert!(
1488                SigningPolicy::default()
1489                    .check_digest_algorithm(algorithm)
1490                    .is_err()
1491            );
1492            let verification = VerificationPolicy {
1493                digest_algorithms: Some(HashSet::from([algorithm])),
1494                ..VerificationPolicy::default()
1495            };
1496            let signing = SigningPolicy {
1497                digest_algorithms: Some(HashSet::from([algorithm])),
1498                ..SigningPolicy::default()
1499            };
1500            assert!(verification.check_digest_algorithm(algorithm).is_ok());
1501            assert!(signing.check_digest_algorithm(algorithm).is_ok());
1502            assert!(
1503                verification
1504                    .check_digest_algorithm(DigestAlgorithm::Sha256)
1505                    .is_err()
1506            );
1507            assert!(
1508                signing
1509                    .check_digest_algorithm(DigestAlgorithm::Sha256)
1510                    .is_err()
1511            );
1512        }
1513    }
1514
1515    #[cfg(feature = "xmldsig")]
1516    #[test]
1517    fn certificate_permissions_keep_exact_pss_and_compatibility_distinct() {
1518        use crate::provider::X509SignatureAlgorithm;
1519        use crate::xmldsig::{DigestAlgorithm, PqAlgorithm};
1520        // Compatibility permission cannot impose an arbitrary PSS salt list;
1521        // exact allowlists must still distinguish every parameter tuple.
1522        let exact = X509SignatureAlgorithm::RsaPss {
1523            digest: DigestAlgorithm::Sha256,
1524            mgf_digest: DigestAlgorithm::Sha256,
1525            salt_len: 32,
1526        };
1527        let allowed = CertificateSignatureAlgorithms::Allowlist(HashSet::from([exact]));
1528        assert!(
1529            KeyTrustPolicy::check_certificate_signature_algorithm(exact, Some(&allowed)).is_ok()
1530        );
1531        for salt_len in [0, 17, 64, 255] {
1532            let method = X509SignatureAlgorithm::RsaPss {
1533                digest: DigestAlgorithm::Sha256,
1534                mgf_digest: DigestAlgorithm::Sha256,
1535                salt_len,
1536            };
1537            assert!(
1538                KeyTrustPolicy::check_certificate_signature_algorithm(method, Some(&allowed))
1539                    .is_err()
1540            );
1541            assert!(
1542                KeyTrustPolicy::check_certificate_signature_algorithm(
1543                    method,
1544                    Some(&CertificateSignatureAlgorithms::AllSupported)
1545                )
1546                .is_ok()
1547            );
1548        }
1549        let pq = X509SignatureAlgorithm::PostQuantum(PqAlgorithm::MlDsa44);
1550        assert!(KeyTrustPolicy::check_certificate_signature_algorithm(pq, None).is_err());
1551        assert!(
1552            KeyTrustPolicy::check_certificate_signature_algorithm(
1553                pq,
1554                Some(&CertificateSignatureAlgorithms::AllSupported)
1555            )
1556            .is_ok()
1557        );
1558    }
1559
1560    #[test]
1561    fn resource_policy_cannot_exceed_implementation_ceiling() {
1562        let policy = ResourcePolicy {
1563            max_xml_nodes: 100_001,
1564            ..ResourcePolicy::default()
1565        };
1566        assert!(matches!(
1567            policy.validate(),
1568            Err(PolicyViolation::ResourceLimit {
1569                resource: resource_name::XML_NODES,
1570                maximum: 100_000,
1571                actual: 100_001,
1572            })
1573        ));
1574    }
1575
1576    #[test]
1577    fn xml_parse_work_policy_cannot_exceed_implementation_ceiling() {
1578        let policy = ResourcePolicy {
1579            max_xml_parse_work_bytes: crate::hard_limits::XML_PARSE_WORK_BYTE_CEILING
1580                .saturating_add(1),
1581            ..ResourcePolicy::default()
1582        };
1583
1584        assert_eq!(
1585            policy.validate(),
1586            Err(PolicyViolation::ResourceLimit {
1587                resource: resource_name::XML_PARSE_WORK_BYTES,
1588                maximum: crate::hard_limits::XML_PARSE_WORK_BYTE_CEILING,
1589                actual: crate::hard_limits::XML_PARSE_WORK_BYTE_CEILING.saturating_add(1),
1590            })
1591        );
1592    }
1593
1594    #[test]
1595    fn every_resource_policy_field_obeys_its_hard_ceiling() {
1596        // Each public tuning knob is only a stricter operational limit; none
1597        // may raise the implementation's allocation ceiling. Exact diagnostics
1598        // also catch a field accidentally paired with another field's ceiling.
1599        type Case = (&'static str, usize, fn(&mut ResourcePolicy) -> &mut usize);
1600        let cases: &[Case] = &[
1601            (
1602                resource_name::XML_NODES,
1603                crate::hard_limits::XML_DOCUMENT_NODE_CEILING as usize,
1604                |p| &mut p.max_xml_nodes,
1605            ),
1606            (
1607                resource_name::XML_DEPTH,
1608                crate::hard_limits::XML_DOCUMENT_DEPTH_CEILING,
1609                |p| &mut p.max_xml_depth,
1610            ),
1611            (
1612                resource_name::XML_NAMESPACE_BINDINGS,
1613                crate::hard_limits::XML_NAMESPACE_BINDING_CEILING,
1614                |p| &mut p.max_xml_namespace_bindings,
1615            ),
1616            (
1617                resource_name::SIGNATURE_REFERENCES,
1618                crate::hard_limits::SIGNATURE_REFERENCE_CEILING,
1619                |p| &mut p.max_references,
1620            ),
1621            (
1622                resource_name::VERIFICATION_SIGNATURES,
1623                crate::hard_limits::VERIFICATION_SIGNATURE_CEILING,
1624                |p| &mut p.max_signatures,
1625            ),
1626            (
1627                resource_name::REFERENCE_TRANSFORMS,
1628                crate::hard_limits::REFERENCE_TRANSFORM_CEILING,
1629                |p| &mut p.max_transforms_per_reference,
1630            ),
1631            (
1632                resource_name::XML_BASE_COMPONENTS,
1633                crate::hard_limits::XML_BASE_COMPONENT_CEILING,
1634                |p| &mut p.max_xml_base_components,
1635            ),
1636            (
1637                resource_name::XML_BASE_RESOLUTION_BYTES,
1638                crate::hard_limits::XML_BASE_RESOLUTION_BYTE_CEILING,
1639                |p| &mut p.max_xml_base_resolution_bytes,
1640            ),
1641            (
1642                resource_name::CANONICALIZED_BYTES,
1643                crate::hard_limits::CANONICALIZED_SIGNATURE_DATA_BYTE_CEILING,
1644                |p| &mut p.max_canonicalized_bytes,
1645            ),
1646            (
1647                resource_name::EXTERNAL_RESOURCE_BYTES,
1648                crate::hard_limits::EXTERNAL_RESOURCE_BYTE_CEILING,
1649                |p| &mut p.max_external_resource_bytes,
1650            ),
1651            (
1652                resource_name::AGGREGATE_EXTERNAL_RESOURCE_BYTES,
1653                crate::hard_limits::EXTERNAL_RESOURCE_TOTAL_BYTE_CEILING,
1654                |p| &mut p.max_external_resource_total_bytes,
1655            ),
1656            (
1657                resource_name::ENCRYPTION_PLAINTEXT_BYTES,
1658                crate::hard_limits::ENCRYPTION_PLAINTEXT_BYTE_CEILING,
1659                |p| &mut p.max_encryption_plaintext_bytes,
1660            ),
1661            (
1662                resource_name::XML_DOCUMENT,
1663                crate::hard_limits::XML_DOCUMENT_BYTE_CEILING,
1664                |p| &mut p.max_xml_document_bytes,
1665            ),
1666            (
1667                resource_name::XML_PARSE_WORK_BYTES,
1668                crate::hard_limits::XML_PARSE_WORK_BYTE_CEILING,
1669                |p| &mut p.max_xml_parse_work_bytes,
1670            ),
1671            (
1672                resource_name::ENCRYPTION_RECIPIENTS,
1673                crate::hard_limits::ENCRYPTION_RECIPIENT_CEILING,
1674                |p| &mut p.max_encryption_recipients,
1675            ),
1676            (
1677                resource_name::ENCRYPTION_METADATA_BYTES,
1678                crate::hard_limits::ENCRYPTION_METADATA_BYTE_CEILING,
1679                |p| &mut p.max_encryption_metadata_bytes,
1680            ),
1681            (
1682                resource_name::KEY_CANDIDATES,
1683                crate::hard_limits::KEY_CANDIDATE_CEILING,
1684                |p| &mut p.max_key_candidates,
1685            ),
1686            (
1687                resource_name::KEY_IMPORT_KDF_WORK,
1688                crate::hard_limits::KEY_IMPORT_KDF_WORK_CEILING as usize,
1689                |p| &mut p.max_key_import_kdf_work,
1690            ),
1691            (
1692                resource_name::KEY_IMPORT_KDF_MEMORY,
1693                crate::hard_limits::KEY_IMPORT_KDF_MEMORY_CEILING,
1694                |p| &mut p.max_key_import_kdf_memory_bytes,
1695            ),
1696            (
1697                resource_name::KEY_INFO_REFERENCE_DEPTH,
1698                crate::hard_limits::KEY_INFO_REFERENCE_DEPTH_CEILING,
1699                |p| &mut p.max_key_info_reference_depth,
1700            ),
1701            (
1702                resource_name::BASE64_TRANSFORM_INPUT_BYTES,
1703                crate::hard_limits::BASE64_TRANSFORM_INPUT_BYTE_CEILING,
1704                |p| &mut p.max_base64_transform_input_bytes,
1705            ),
1706            (
1707                resource_name::BASE64_TRANSFORM_OUTPUT_BYTES,
1708                crate::hard_limits::BASE64_TRANSFORM_OUTPUT_BYTE_CEILING,
1709                |p| &mut p.max_base64_transform_output_bytes,
1710            ),
1711            (
1712                resource_name::XPATH_EXPRESSIONS,
1713                crate::hard_limits::XPATH_EXPRESSION_COUNT_CEILING,
1714                |p| &mut p.max_xpath_expressions,
1715            ),
1716            (
1717                resource_name::XPATH_EXPRESSION_BYTES,
1718                crate::hard_limits::XPATH_EXPRESSION_BYTE_CEILING,
1719                |p| &mut p.max_xpath_expression_bytes,
1720            ),
1721            (
1722                resource_name::XPATH_EXPRESSION_COMPLEXITY,
1723                crate::hard_limits::XPATH_EXPRESSION_COMPLEXITY_CEILING,
1724                |p| &mut p.max_xpath_expression_complexity,
1725            ),
1726            (
1727                resource_name::XPATH_CONTEXT_EVALUATIONS,
1728                crate::hard_limits::XPATH_CONTEXT_EVALUATION_CEILING,
1729                |p| &mut p.max_xpath_context_evaluations,
1730            ),
1731            (
1732                resource_name::XPATH_EVALUATION_WORK,
1733                crate::hard_limits::XPATH_EVALUATION_WORK_CEILING,
1734                |p| &mut p.max_xpath_evaluation_work,
1735            ),
1736            (
1737                resource_name::XPATH_MIRROR_STRING_BYTES,
1738                crate::hard_limits::XPATH_MIRROR_STRING_BYTE_CEILING,
1739                |p| &mut p.max_xpath_mirror_string_bytes,
1740            ),
1741            (
1742                resource_name::XPATH_STRING_WORK_BYTES,
1743                crate::hard_limits::XPATH_STRING_WORK_BYTE_CEILING,
1744                |p| &mut p.max_xpath_string_work_bytes,
1745            ),
1746            (
1747                resource_name::XPATH_NAMESPACE_BINDINGS,
1748                crate::hard_limits::XPATH_NAMESPACE_BINDING_CEILING,
1749                |p| &mut p.max_xpath_namespace_bindings,
1750            ),
1751            (
1752                resource_name::XPATH_NAMESPACE_BYTES,
1753                crate::hard_limits::XPATH_NAMESPACE_BYTE_CEILING,
1754                |p| &mut p.max_xpath_namespace_bytes,
1755            ),
1756            (
1757                resource_name::XPATH_FILTERS,
1758                crate::hard_limits::XPATH_FILTER_COUNT_CEILING,
1759                |p| &mut p.max_xpath_filters,
1760            ),
1761            (
1762                resource_name::NODE_SET_FILTER_WORK,
1763                crate::hard_limits::NODE_SET_FILTER_WORK_CEILING,
1764                |p| &mut p.max_node_set_filter_work,
1765            ),
1766            (
1767                resource_name::OPC_PARAMETER_BYTES,
1768                crate::hard_limits::OPC_PARAMETER_BYTE_CEILING,
1769                |p| &mut p.max_opc_parameter_bytes,
1770            ),
1771            (
1772                resource_name::OPC_WORKSPACE_BYTES,
1773                crate::hard_limits::OPC_WORKSPACE_BYTE_CEILING,
1774                |p| &mut p.max_opc_workspace_bytes,
1775            ),
1776            (
1777                resource_name::NODE_SET_ENTRIES,
1778                crate::hard_limits::NODE_SET_ENTRY_CEILING,
1779                |p| &mut p.max_node_set_entries,
1780            ),
1781            (
1782                resource_name::NODE_SET_OWNED_STRING_BYTES,
1783                crate::hard_limits::NODE_SET_OWNED_STRING_BYTE_CEILING,
1784                |p| &mut p.max_node_set_owned_string_bytes,
1785            ),
1786            (
1787                resource_name::NODE_SET_CUMULATIVE_OWNED_STRING_BYTES,
1788                crate::hard_limits::NODE_SET_CUMULATIVE_OWNED_STRING_BYTE_CEILING,
1789                |p| &mut p.max_node_set_cumulative_owned_string_bytes,
1790            ),
1791        ];
1792
1793        for &(resource, ceiling, field) in cases {
1794            let mut policy = ResourcePolicy::default();
1795            let actual = ceiling.saturating_add(1);
1796            *field(&mut policy) = actual;
1797            assert_eq!(
1798                policy.validate(),
1799                Err(PolicyViolation::ResourceLimit {
1800                    resource,
1801                    maximum: ceiling,
1802                    actual,
1803                }),
1804                "wrong hard-ceiling validation for {resource}",
1805            );
1806        }
1807    }
1808
1809    #[test]
1810    fn resource_policy_accepts_zero_as_a_deny_all_ceiling() {
1811        // Zero is a valid policy decision for resources that an operation can
1812        // avoid consuming; runtime checks must reject only actual non-zero use.
1813        let policy = ResourcePolicy {
1814            max_xml_nodes: 0,
1815            max_xml_depth: 0,
1816            max_xml_namespace_bindings: 0,
1817            max_references: 0,
1818            max_signatures: 0,
1819            max_transforms_per_reference: 0,
1820            max_xml_base_components: 0,
1821            max_xml_base_resolution_bytes: 0,
1822            max_canonicalized_bytes: 0,
1823            max_external_resource_bytes: 0,
1824            max_external_resource_total_bytes: 0,
1825            max_encryption_plaintext_bytes: 0,
1826            max_xml_document_bytes: 0,
1827            max_xml_parse_work_bytes: 0,
1828            max_encryption_recipients: 0,
1829            max_encryption_metadata_bytes: 0,
1830            max_key_candidates: 0,
1831            max_key_import_kdf_work: 0,
1832            max_key_import_kdf_memory_bytes: 0,
1833            max_key_info_reference_depth: 0,
1834            max_base64_transform_input_bytes: 0,
1835            max_base64_transform_output_bytes: 0,
1836            max_xpath_expressions: 0,
1837            max_xpath_expression_bytes: 0,
1838            max_xpath_expression_complexity: 0,
1839            max_xpath_context_evaluations: 0,
1840            max_xpath_evaluation_work: 0,
1841            max_xpath_mirror_string_bytes: 0,
1842            max_xpath_string_work_bytes: 0,
1843            max_xpath_namespace_bindings: 0,
1844            max_xpath_namespace_bytes: 0,
1845            max_xpath_filters: 0,
1846            max_node_set_filter_work: 0,
1847            max_node_set_entries: 0,
1848            max_node_set_owned_string_bytes: 0,
1849            max_node_set_cumulative_owned_string_bytes: 0,
1850            max_opc_workspace_bytes: 0,
1851            max_opc_parameter_bytes: 0,
1852        };
1853
1854        assert_eq!(policy.validate(), Ok(()));
1855    }
1856
1857    #[cfg(any(feature = "xmldsig", feature = "xmlenc"))]
1858    #[test]
1859    fn node_set_policy_has_headroom_above_its_default() {
1860        let mut policy = ResourcePolicy::default();
1861        assert_eq!(policy.max_node_set_entries, 262_144);
1862        assert_eq!(policy.validate(), Ok(()));
1863
1864        policy.max_node_set_entries = 524_288;
1865        assert_eq!(policy.validate(), Ok(()));
1866
1867        policy.max_node_set_entries = 524_289;
1868        assert_eq!(
1869            policy.validate(),
1870            Err(PolicyViolation::ResourceLimit {
1871                resource: resource_name::NODE_SET_ENTRIES,
1872                maximum: 524_288,
1873                actual: 524_289,
1874            })
1875        );
1876    }
1877
1878    #[cfg(any(feature = "xmldsig", feature = "xmlenc"))]
1879    #[test]
1880    fn rsa_key_policy_enforces_structure_range_and_explicit_relaxation() {
1881        let secure = RsaKeyPolicy::default();
1882        assert!(matches!(
1883            secure.validate_components("test", &[0x80; 128], &[1, 0, 1]),
1884            Err(PolicyViolation::KeySize {
1885                minimum_bits: 2048,
1886                maximum_bits: 8192,
1887                actual_bits: 1024,
1888                ..
1889            })
1890        ));
1891        let mut short_2048_width = [0_u8; 256];
1892        short_2048_width[0] = 1;
1893        assert!(matches!(
1894            secure.validate_components("test", &short_2048_width, &[1, 0, 1]),
1895            Err(PolicyViolation::KeySize {
1896                minimum_bits: 2048,
1897                actual_bits: 2041,
1898                ..
1899            })
1900        ));
1901        assert!(matches!(
1902            secure.validate_components("test", &[1; 1025], &[1, 0, 1]),
1903            Err(PolicyViolation::KeySize {
1904                actual_bits: 8193,
1905                ..
1906            })
1907        ));
1908        assert!(matches!(
1909            secure.validate_components("test", &[0x80; 256], &[2]),
1910            Err(PolicyViolation::InvalidKeyMaterial { .. })
1911        ));
1912        assert_eq!(
1913            secure.validate_components("test", &[0x80; 256], &[0x80, 0, 0, 1]),
1914            Ok(256),
1915            "normalized RSA components encode the exponent as unsigned bytes"
1916        );
1917
1918        let compatibility = RsaKeyPolicy {
1919            minimum_modulus_bits: 1024,
1920        };
1921        assert_eq!(
1922            compatibility.validate_components("test", &[0x80; 128], &[1, 0, 1]),
1923            Ok(128)
1924        );
1925        assert!(
1926            RsaKeyPolicy {
1927                minimum_modulus_bits: 2047,
1928            }
1929            .validate()
1930            .is_err()
1931        );
1932    }
1933
1934    #[cfg(feature = "xmldsig")]
1935    #[test]
1936    fn mandatory_x509_limits_report_the_nonzero_requirement() {
1937        // A lower-bound violation must not be reported as exceeding the upper
1938        // ceiling: that diagnostic points callers toward the wrong correction.
1939        let policy = KeyTrustPolicy {
1940            max_x509_chain_depth: 0,
1941            ..KeyTrustPolicy::default()
1942        };
1943
1944        let error = policy.validate().expect_err("zero depth must be rejected");
1945        assert!(
1946            error.to_string().contains("must be nonzero"),
1947            "unexpected lower-bound diagnostic: {error}"
1948        );
1949    }
1950
1951    #[cfg(feature = "xmldsig")]
1952    #[test]
1953    fn custom_extended_key_purposes_require_valid_oid_arcs() {
1954        // The typed policy rejects impossible OIDs when the immutable snapshot
1955        // is validated instead of silently making the purpose unmatchable.
1956        let mut policy = KeyTrustPolicy::default();
1957        policy
1958            .allowed_extended_key_usages
1959            .insert(ExtendedKeyPurpose::Other(vec![1, 40, 7]));
1960
1961        assert!(matches!(
1962            policy.validate(),
1963            Err(PolicyViolation::KeyTrust {
1964                reason: "custom extended key purposes must contain valid OID arcs",
1965            })
1966        ));
1967    }
1968
1969    #[cfg(feature = "xmldsig")]
1970    #[test]
1971    fn crl_checking_requires_x509_chain_validation() {
1972        // CRLs authenticate through the validated issuer path. Accepting this
1973        // combination would advertise a security control the resolver skips.
1974        let policy = KeyTrustPolicy {
1975            check_crls: true,
1976            ..KeyTrustPolicy::default()
1977        };
1978
1979        assert!(matches!(
1980            policy.validate(),
1981            Err(PolicyViolation::KeyTrust {
1982                reason: "CRL checking requires X.509 chain validation"
1983            })
1984        ));
1985    }
1986
1987    #[cfg(feature = "xmldsig")]
1988    #[test]
1989    fn legacy_signature_algorithms_require_independent_policy_opt_ins() {
1990        let legacy = [
1991            SignatureAlgorithm::RsaSha1,
1992            SignatureAlgorithm::DsaSha1,
1993            SignatureAlgorithm::HmacSha1,
1994            SignatureAlgorithm::EcdsaSha1,
1995        ];
1996        let mut policy = VerificationPolicy::default();
1997
1998        for algorithm in legacy {
1999            assert!(matches!(
2000                policy.check_signature_algorithm(algorithm),
2001                Err(PolicyViolation::Algorithm { .. })
2002            ));
2003            policy
2004                .key_trust
2005                .allowed_legacy_signature_algorithms
2006                .insert(algorithm);
2007            assert_eq!(policy.check_signature_algorithm(algorithm), Ok(()));
2008            policy
2009                .key_trust
2010                .allowed_legacy_signature_algorithms
2011                .remove(&algorithm);
2012        }
2013    }
2014
2015    #[cfg(feature = "xmldsig")]
2016    #[test]
2017    fn dsa_key_policy_enforces_configured_minimum_and_hard_ceiling() {
2018        let policy = DsaKeyPolicy::default();
2019
2020        assert!(matches!(
2021            policy.validate_modulus_bits(1024),
2022            Err(PolicyViolation::KeySize {
2023                key_type: "DSA",
2024                minimum_bits: 2048,
2025                maximum_bits: 3072,
2026                actual_bits: 1024,
2027                ..
2028            })
2029        ));
2030        assert_eq!(policy.validate_modulus_bits(2048), Ok(()));
2031        assert!(matches!(
2032            policy.validate_modulus_bits(4096),
2033            Err(PolicyViolation::KeySize {
2034                key_type: "DSA",
2035                minimum_bits: 2048,
2036                maximum_bits: 3072,
2037                actual_bits: 4096,
2038                ..
2039            })
2040        ));
2041    }
2042
2043    #[cfg(feature = "xmldsig")]
2044    #[test]
2045    fn hmac_output_policy_cannot_weaken_the_xmldsig_floor() {
2046        // Caller policy may tighten but cannot weaken XMLDSig section 4.4.2:
2047        // truncation is at least 80 bits and at least half the digest width.
2048        let compatibility = HmacPolicy {
2049            minimum_key_bits: 40,
2050            minimum_output_bits: 40,
2051        };
2052
2053        assert_eq!(
2054            compatibility.validate_output(SignatureAlgorithm::HmacSha1, 80),
2055            Ok(())
2056        );
2057        assert!(matches!(
2058            compatibility.validate_output(SignatureAlgorithm::HmacSha1, 72),
2059            Err(PolicyViolation::HmacOutputLength { minimum: 80, .. })
2060        ));
2061        assert!(matches!(
2062            compatibility.validate_output(SignatureAlgorithm::HmacSha256, 120),
2063            Err(PolicyViolation::HmacOutputLength { minimum: 128, .. })
2064        ));
2065    }
2066
2067    #[cfg(feature = "xmldsig")]
2068    #[test]
2069    fn documented_xmldsig_limits_match_hard_limits() {
2070        // Public deployment guidance must change in the same commit as the
2071        // implementation ceilings from which these values are derived.
2072        let docs = include_str!("../docs/xmldsig.md");
2073        let mib = 1024 * 1024;
2074        assert!(docs.contains(&format!(
2075            "Individual resources are limited to {} MiB",
2076            crate::hard_limits::EXTERNAL_RESOURCE_BYTE_CEILING / mib
2077        )));
2078        assert!(docs.contains(&format!(
2079            "complete map to {} MiB",
2080            crate::hard_limits::EXTERNAL_RESOURCE_TOTAL_BYTE_CEILING / mib
2081        )));
2082        assert!(docs.contains(&format!(
2083            "ceilings are {} components and {} MiB per operation",
2084            crate::hard_limits::XML_BASE_COMPONENT_CEILING,
2085            crate::hard_limits::XML_BASE_RESOLUTION_BYTE_CEILING / mib
2086        )));
2087    }
2088}