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