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