Skip to main content

xml_sec/xmlenc/
decrypt.rs

1//! XMLEnc decryption entry point and key resolvers.
2
3use std::{borrow::Cow, fmt, sync::Arc};
4
5#[cfg(test)]
6use crate::xml::dom::Document;
7#[cfg(test)]
8use base64::Engine as _;
9
10use crate::document::{DocumentParseSettings, XmlDocument, XmlParseWorkBudget};
11use crate::operation::{
12    OperationExecutionContext, OperationNodeId, OperationNodeKind, OperationPlanError,
13    OperationStage,
14};
15use rsa::RsaPrivateKey;
16
17use super::parse::validate_encrypted_data_metadata;
18use super::types::{MAX_CIPHER_VALUE_BASE64_LEN, XMLENC_NS, validate_ciphertext_framing};
19use super::{
20    DataEncryptionAlgorithm, DecryptedContent, EncryptedData, EncryptedDataType, EncryptedKey,
21    KeyTransportAlgorithm, KeyWrapAlgorithm, OaepDigestAlgorithm, RsaOaepParameters, XmlEncError,
22    map_document_error,
23};
24
25#[cfg(test)]
26use super::parse_encrypted_data;
27
28/// Aggregate key-candidate work allowance for one cryptographic operation.
29///
30/// Resolver implementations must consume one unit before each key lookup or
31/// unwrap attempt. A single budget is shared across direct and recipient keys.
32#[derive(Debug)]
33pub struct KeyCandidateBudget {
34    maximum: usize,
35    remaining: usize,
36    pub(super) key_establishment: super::key_establishment_budget::KeyEstablishmentUsage,
37}
38
39impl KeyCandidateBudget {
40    /// Create the fixed implementation-wide budget for one operation.
41    pub fn for_operation() -> Self {
42        Self::with_limit(crate::hard_limits::KEY_CANDIDATE_CEILING)
43    }
44
45    /// Create a budget from a validated operation policy ceiling.
46    pub fn with_limit(maximum: usize) -> Self {
47        Self {
48            maximum,
49            remaining: maximum,
50            key_establishment: Default::default(),
51        }
52    }
53
54    /// Number of candidate attempts still available to this operation.
55    pub const fn remaining(&self) -> usize {
56        self.remaining
57    }
58
59    /// Derive a key against the caller's operation snapshot, retaining all KDF
60    /// reservations in this same budget across nested sources and retries.
61    /// Resolvers must forward the snapshot received by their policy-aware entry
62    /// point; they must never choose a profile from document content.
63    pub fn derive_key(
64        &mut self,
65        provider: &dyn crate::provider::CryptoProvider,
66        policy: &crate::policy::DecryptionPolicy,
67        parameters: &crate::provider::KdfParameters<'_>,
68        secret: &[u8],
69    ) -> Result<zeroize::Zeroizing<Vec<u8>>, XmlEncError> {
70        self.key_establishment
71            .derive_key(&policy.key_establishment, provider, parameters, secret)
72    }
73
74    /// Establish a shared secret and derive its consuming key after reserving
75    /// both outputs and checking both permissions before either provider call.
76    /// Candidate allowance includes scalar multiplication and derivation.
77    pub fn agree_and_derive(
78        &mut self,
79        provider: &dyn crate::provider::CryptoProvider,
80        policy: &crate::policy::DecryptionPolicy,
81        key: &dyn crate::provider::KeyAgreementKey,
82        agreement: &crate::provider::KeyAgreementParameters<'_>,
83        parameters: &crate::provider::KdfParameters<'_>,
84    ) -> Result<zeroize::Zeroizing<Vec<u8>>, XmlEncError> {
85        self.consume(2)?;
86        self.key_establishment.agree_and_derive(
87            &policy.key_establishment,
88            provider,
89            key,
90            agreement,
91            parameters,
92        )
93    }
94
95    /// Charge attempted candidate work before performing it.
96    pub fn consume(&mut self, count: usize) -> Result<(), XmlEncError> {
97        self.require_available(count)?;
98        self.remaining -= count;
99        Ok(())
100    }
101
102    pub(super) fn require_available(&self, count: usize) -> Result<(), XmlEncError> {
103        if count > self.remaining {
104            return Err(crate::policy::PolicyViolation::ResourceLimit {
105                resource: crate::policy::resource_name::KEY_CANDIDATES,
106                maximum: self.maximum,
107                actual: self
108                    .maximum
109                    .saturating_sub(self.remaining)
110                    .saturating_add(count),
111            }
112            .into());
113        }
114        Ok(())
115    }
116
117    fn account_returned_candidates(
118        &mut self,
119        remaining_before: usize,
120        returned: usize,
121    ) -> Result<(), XmlEncError> {
122        let resolver_charged = remaining_before.saturating_sub(self.remaining);
123        self.consume(returned.saturating_sub(resolver_charged))
124    }
125}
126
127/// Supplies a content-encryption key for parsed XMLEnc data.
128/// A source resolved for a wrapping algorithm. Its output is never implicitly
129/// treated as a CEK or exported from a non-exportable provider handle.
130pub enum KeyEncryptionKeySource<'a> {
131    /// Experimental KEM producing this operation's wrapping key.
132    Encapsulation(&'a crate::key_establishment::EncapsulationMechanism),
133    /// Application-owned key material without transported establishment hints.
134    Direct,
135    /// A key transporting this operation's wrapping key.
136    Encrypted(&'a EncryptedKey),
137    /// Agreement producing this operation's wrapping key.
138    Agreement(&'a super::AgreementMethod),
139    /// Derivation producing this operation's wrapping key.
140    Derived(&'a super::DerivedKey),
141}
142
143/// Supplies keys for an XMLEnc operation under its immutable policy snapshot.
144pub trait DecryptionKeyResolver {
145    /// Resolve an experimental KEM source under the operation snapshot. Raw
146    /// content-key resolvers refuse by default rather than bypassing the KEM.
147    fn resolve_encapsulation_content_keys_with_policy(
148        &self,
149        _provider: &dyn crate::provider::CryptoProvider,
150        _algorithm: DataEncryptionAlgorithm,
151        _descriptor: &crate::key_establishment::EncapsulationMechanism,
152        _policy: &crate::policy::DecryptionPolicy,
153        _budget: &mut KeyCandidateBudget,
154    ) -> Result<Vec<crate::provider::RecoveredContentKey>, XmlEncError> {
155        Err(XmlEncError::KeyNotFound)
156    }
157    /// Resolve an actual KEK purpose; default refusal prevents incorrectly
158    /// adapting a content algorithm merely because its key width happens to fit.
159    fn resolve_key_encryption_keys_with_policy(
160        &self,
161        _provider: &dyn crate::provider::CryptoProvider,
162        _algorithm: KeyWrapAlgorithm,
163        _source: KeyEncryptionKeySource<'_>,
164        _policy: &crate::policy::DecryptionPolicy,
165        _budget: &mut KeyCandidateBudget,
166    ) -> Result<Vec<crate::provider::RecoveredContentKey>, XmlEncError> {
167        Err(XmlEncError::KeyNotFound)
168    }
169    /// Resolve an agreement under the operation snapshot and shared allowance.
170    /// Default refusal prevents a raw content key from bypassing AgreementMethod.
171    fn resolve_agreement_content_keys_with_policy(
172        &self,
173        _provider: &dyn crate::provider::CryptoProvider,
174        _algorithm: DataEncryptionAlgorithm,
175        _descriptor: &super::AgreementMethod,
176        _policy: &crate::policy::DecryptionPolicy,
177        _budget: &mut KeyCandidateBudget,
178    ) -> Result<Vec<crate::provider::RecoveredContentKey>, XmlEncError> {
179        Err(XmlEncError::KeyNotFound)
180    }
181    /// Resolve a transported derivation descriptor using the same immutable
182    /// operation policy and cumulative candidate/KDF allowance. The default
183    /// refuses it; a raw content-key resolver must not bypass derivation.
184    fn resolve_derived_content_keys_with_policy(
185        &self,
186        _provider: &dyn crate::provider::CryptoProvider,
187        _algorithm: DataEncryptionAlgorithm,
188        _descriptor: &super::DerivedKey,
189        _policy: &crate::policy::DecryptionPolicy,
190        _budget: &mut KeyCandidateBudget,
191    ) -> Result<Vec<crate::provider::RecoveredContentKey>, XmlEncError> {
192        Err(XmlEncError::KeyNotFound)
193    }
194    /// Resolve operation candidates without dropping implicit-rejection state.
195    /// Wrappers around recipient resolvers must forward this method; converting
196    /// recovered candidates to raw bytes loses the final content acceptance gate.
197    fn resolve_content_keys_with_policy(
198        &self,
199        provider: &dyn crate::provider::CryptoProvider,
200        algorithm: DataEncryptionAlgorithm,
201        encrypted_key: Option<&EncryptedKey>,
202        policy: &crate::policy::DecryptionPolicy,
203        budget: &mut KeyCandidateBudget,
204    ) -> Result<Vec<crate::provider::RecoveredContentKey>, XmlEncError> {
205        self.resolve_key_candidates_with_policy(provider, algorithm, encrypted_key, policy, budget)
206            .map(|keys| {
207                keys.into_iter()
208                    .map(crate::provider::RecoveredContentKey::confirmed)
209                    .collect()
210            })
211    }
212    /// Resolve under the operation's immutable snapshot. RSA resolvers enforce
213    /// `rsa_keys` before provider recovery; wrappers must forward this snapshot.
214    ///
215    /// The default supports direct content keys only and returns
216    /// [`XmlEncError::KeyNotFound`] for recipients without invoking legacy
217    /// resolution. Recipient resolvers must override this method: recovered
218    /// symmetric bytes cannot prove the original key met the operation policy.
219    fn resolve_key_candidates_with_policy(
220        &self,
221        provider: &dyn crate::provider::CryptoProvider,
222        algorithm: DataEncryptionAlgorithm,
223        encrypted_key: Option<&EncryptedKey>,
224        policy: &crate::policy::DecryptionPolicy,
225        budget: &mut KeyCandidateBudget,
226    ) -> Result<Vec<Vec<u8>>, XmlEncError> {
227        policy.validate()?;
228        if encrypted_key.is_some() {
229            return Err(XmlEncError::KeyNotFound);
230        }
231        self.resolve_key_candidates(provider, algorithm, None, budget)
232    }
233
234    /// Resolve the symmetric key for `algorithm`, optionally unwrapping `encrypted_key`.
235    fn resolve_key(
236        &self,
237        provider: &dyn crate::provider::CryptoProvider,
238        algorithm: DataEncryptionAlgorithm,
239        encrypted_key: Option<&EncryptedKey>,
240    ) -> Result<Vec<u8>, XmlEncError>;
241
242    /// Resolve ordered candidate keys for one prepared decryption operation.
243    ///
244    /// The default preserves single-key resolver behavior. Key rings override
245    /// this method so parsing, policy validation, and ciphertext decoding occur
246    /// once while only authenticated primitive decryption is retried. Overrides
247    /// must consume the shared budget before every lookup or unwrap attempt.
248    /// The context also accounts for any returned candidates an implementation
249    /// did not explicitly charge. Returning [`XmlEncError::Policy`] rejects the
250    /// complete operation and never advances to another key source; use a
251    /// candidate-local error such as [`XmlEncError::KeyNotFound`] when later
252    /// ordered sources are still eligible.
253    fn resolve_key_candidates(
254        &self,
255        provider: &dyn crate::provider::CryptoProvider,
256        algorithm: DataEncryptionAlgorithm,
257        encrypted_key: Option<&EncryptedKey>,
258        budget: &mut KeyCandidateBudget,
259    ) -> Result<Vec<Vec<u8>>, XmlEncError> {
260        budget.consume(1)?;
261        self.resolve_key(provider, algorithm, encrypted_key)
262            .map(|key| vec![key])
263    }
264}
265
266/// Derive a direct content key or wrapping key from caller-owned secret
267/// material and parsed KDF parameters. The operation, not this request object,
268/// supplies permission and cumulative allowance. Secret material is borrowed.
269pub struct DerivedKeyDecryptor<'a> {
270    method: &'a super::KeyDerivationMethod,
271    input: DerivedKeyInput<'a>,
272    purpose: DerivedKeyPurpose,
273    master_key_name: Option<&'a str>,
274}
275
276/// Application-owned key-establishment input; private handles and secret bytes
277/// are borrowed, and document data cannot replace their provenance.
278pub enum DerivedKeyInput<'a> {
279    /// Caller-selected secret or password octets.
280    Secret(&'a [u8]),
281    /// Caller-selected private handle and peer parameters for agreement.
282    Agreement {
283        /// Opaque private key belonging to the selected provider.
284        key: &'a dyn crate::provider::KeyAgreementKey,
285        /// Encoded public peer and the requested agreement algorithm.
286        parameters: crate::provider::KeyAgreementParameters<'a>,
287    },
288}
289
290enum DerivedKeyPurpose {
291    Content(DataEncryptionAlgorithm),
292    Wrapping(KeyWrapAlgorithm),
293}
294
295impl<'a> DerivedKeyDecryptor<'a> {
296    /// Bind explicitly selected KDF parameters to an application-owned secret.
297    /// This does not discover keys or permit algorithms on the caller's behalf.
298    pub fn content(
299        method: &'a super::KeyDerivationMethod,
300        input: DerivedKeyInput<'a>,
301        algorithm: DataEncryptionAlgorithm,
302    ) -> Self {
303        Self {
304            method,
305            input,
306            purpose: DerivedKeyPurpose::Content(algorithm),
307            master_key_name: None,
308        }
309    }
310
311    /// Bind a wrapping-key derivation rather than a content-key derivation.
312    /// Purpose is application context, never inferred from untrusted XML widths.
313    pub fn wrapping(
314        method: &'a super::KeyDerivationMethod,
315        input: DerivedKeyInput<'a>,
316        algorithm: KeyWrapAlgorithm,
317    ) -> Self {
318        Self {
319            method,
320            input,
321            purpose: DerivedKeyPurpose::Wrapping(algorithm),
322            master_key_name: None,
323        }
324    }
325
326    /// Bind XML MasterKeyName to the caller-selected material. A transported
327    /// name never selects or substitutes private material by itself.
328    pub fn master_key_name(mut self, name: &'a str) -> Self {
329        self.master_key_name = Some(name);
330        self
331    }
332
333    fn derive(
334        &self,
335        provider: &dyn crate::provider::CryptoProvider,
336        width: usize,
337        policy: &crate::policy::DecryptionPolicy,
338        budget: &mut KeyCandidateBudget,
339    ) -> Result<zeroize::Zeroizing<Vec<u8>>, XmlEncError> {
340        let parameters = self.method.parameters(width)?;
341        match &self.input {
342            DerivedKeyInput::Secret(secret) => {
343                budget.consume(1)?;
344                budget.derive_key(provider, policy, &parameters, secret)
345            }
346            DerivedKeyInput::Agreement {
347                key,
348                parameters: agreement,
349            } => budget.agree_and_derive(provider, policy, *key, agreement, &parameters),
350        }
351    }
352}
353
354impl fmt::Debug for DerivedKeyDecryptor<'_> {
355    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
356        formatter
357            .debug_struct("DerivedKeyDecryptor")
358            .finish_non_exhaustive()
359    }
360}
361
362impl DecryptionKeyResolver for DerivedKeyDecryptor<'_> {
363    fn resolve_key_encryption_keys_with_policy(
364        &self,
365        provider: &dyn crate::provider::CryptoProvider,
366        algorithm: KeyWrapAlgorithm,
367        source: KeyEncryptionKeySource<'_>,
368        policy: &crate::policy::DecryptionPolicy,
369        budget: &mut KeyCandidateBudget,
370    ) -> Result<Vec<crate::provider::RecoveredContentKey>, XmlEncError> {
371        if !matches!(self.purpose, DerivedKeyPurpose::Wrapping(expected) if expected == algorithm) {
372            return Err(XmlEncError::KeyNotFound);
373        }
374        match source {
375            KeyEncryptionKeySource::Derived(descriptor)
376                if descriptor
377                    .method
378                    .as_ref()
379                    .is_none_or(|method| method == self.method)
380                    && descriptor
381                        .master_key_name
382                        .as_deref()
383                        .is_none_or(|name| Some(name) == self.master_key_name) => {}
384            KeyEncryptionKeySource::Agreement(descriptor) => {
385                let DerivedKeyInput::Agreement { parameters, .. } = &self.input else {
386                    return Err(XmlEncError::KeyNotFound);
387                };
388                if descriptor.algorithm.uri() != parameters.algorithm
389                    || descriptor
390                        .method
391                        .as_ref()
392                        .is_some_and(|method| method != self.method)
393                    || descriptor.originator.is_some()
394                    || descriptor.recipient.is_some()
395                    || !descriptor.nonce.is_empty()
396                    || descriptor.legacy_digest.is_some()
397                {
398                    return Err(XmlEncError::KeyNotFound);
399                }
400            }
401            _ => return Err(XmlEncError::KeyNotFound),
402        }
403        budget.require_available(match self.input {
404            DerivedKeyInput::Secret(_) => 2,
405            DerivedKeyInput::Agreement { .. } => 3,
406        })?;
407        let mut key = self.derive(provider, algorithm.key_len(), policy, budget)?;
408        Ok(vec![crate::provider::RecoveredContentKey::confirmed(
409            core::mem::take(&mut *key),
410        )])
411    }
412    fn resolve_agreement_content_keys_with_policy(
413        &self,
414        provider: &dyn crate::provider::CryptoProvider,
415        algorithm: DataEncryptionAlgorithm,
416        descriptor: &super::AgreementMethod,
417        policy: &crate::policy::DecryptionPolicy,
418        budget: &mut KeyCandidateBudget,
419    ) -> Result<Vec<crate::provider::RecoveredContentKey>, XmlEncError> {
420        let DerivedKeyInput::Agreement { parameters, .. } = &self.input else {
421            return Err(XmlEncError::KeyNotFound);
422        };
423        // This request binds both parties out of band. A transported selector
424        // requires the full expected-descriptor resolver, not silent ignoring.
425        if descriptor.algorithm.uri() != parameters.algorithm
426            || descriptor
427                .method
428                .as_ref()
429                .is_some_and(|method| method != self.method)
430            || descriptor.originator.is_some()
431            || descriptor.recipient.is_some()
432            || !descriptor.nonce.is_empty()
433            || descriptor.legacy_digest.is_some()
434        {
435            return Err(XmlEncError::KeyNotFound);
436        }
437        self.resolve_content_keys_with_policy(provider, algorithm, None, policy, budget)
438    }
439    fn resolve_derived_content_keys_with_policy(
440        &self,
441        provider: &dyn crate::provider::CryptoProvider,
442        algorithm: DataEncryptionAlgorithm,
443        descriptor: &super::DerivedKey,
444        policy: &crate::policy::DecryptionPolicy,
445        budget: &mut KeyCandidateBudget,
446    ) -> Result<Vec<crate::provider::RecoveredContentKey>, XmlEncError> {
447        // XMLEnc 1.1 §5.4.1 leaves AlgorithmID/party validation to the
448        // application. Match its entire expected descriptor BEFORE dispatch,
449        // retaining bit boundaries and preventing document-selected context.
450        // https://www.w3.org/TR/2013/REC-xmlenc-core1-20130411/#sec-ConcatKDF
451        if descriptor
452            .method
453            .as_ref()
454            .is_some_and(|method| method != self.method)
455            || descriptor
456                .master_key_name
457                .as_deref()
458                .is_some_and(|name| Some(name) != self.master_key_name)
459        {
460            return Err(XmlEncError::KeyNotFound);
461        }
462        self.resolve_content_keys_with_policy(provider, algorithm, None, policy, budget)
463    }
464    fn resolve_key(
465        &self,
466        _provider: &dyn crate::provider::CryptoProvider,
467        _algorithm: DataEncryptionAlgorithm,
468        _encrypted_key: Option<&EncryptedKey>,
469    ) -> Result<Vec<u8>, XmlEncError> {
470        // A policy-free legacy callback must not create a fresh KDF allowance.
471        Err(XmlEncError::KeyNotFound)
472    }
473
474    fn resolve_content_keys_with_policy(
475        &self,
476        provider: &dyn crate::provider::CryptoProvider,
477        algorithm: DataEncryptionAlgorithm,
478        encrypted_key: Option<&EncryptedKey>,
479        policy: &crate::policy::DecryptionPolicy,
480        budget: &mut KeyCandidateBudget,
481    ) -> Result<Vec<crate::provider::RecoveredContentKey>, XmlEncError> {
482        policy.validate()?;
483        match (&self.purpose, encrypted_key) {
484            (DerivedKeyPurpose::Content(_), Some(_)) | (DerivedKeyPurpose::Wrapping(_), None) => {
485                return Err(XmlEncError::KeyNotFound);
486            }
487            (DerivedKeyPurpose::Content(expected), None) if *expected != algorithm => {
488                return Err(XmlEncError::KeyNotFound);
489            }
490            _ => {}
491        }
492        let wrap = match encrypted_key {
493            None => None,
494            Some(encrypted_key) => {
495                validate_encrypted_key_policy(encrypted_key, policy)?;
496                encrypted_key.encryption_method.validate_structure()?;
497                Some(KeyWrapAlgorithm::from_uri(
498                    &encrypted_key.encryption_method.algorithm,
499                )?)
500            }
501        };
502        if let (DerivedKeyPurpose::Wrapping(expected), Some(actual)) = (&self.purpose, wrap)
503            && *expected != actual
504        {
505            return Err(XmlEncError::KeyNotFound);
506        }
507        let width = match wrap {
508            Some(wrap) => wrap.key_len(),
509            None => algorithm.key_len(),
510        };
511        // Check the complete attempt before expensive password or scalar work.
512        // Individual stages still charge themselves, retaining failed work and
513        // forwarding this same budget into the existing unwrap implementation.
514        let derivation_attempts = match self.input {
515            DerivedKeyInput::Secret(_) => 1,
516            DerivedKeyInput::Agreement { .. } => 2,
517        };
518        let required = if wrap.is_some() {
519            derivation_attempts + 1
520        } else {
521            derivation_attempts
522        };
523        budget.require_available(required)?;
524        let mut key = self.derive(provider, width, policy, budget)?;
525        if let Some(wrap) = wrap {
526            // Reuse the existing policy-aware recipient path rather than keeping
527            // a second implementation of wrap-family, framing and key checks.
528            return KekDecryptor::borrowed_with_kind(&key, wrap.key_kind())
529                .resolve_content_keys_with_policy(
530                    provider,
531                    algorithm,
532                    encrypted_key,
533                    policy,
534                    budget,
535                );
536        }
537        Ok(vec![crate::provider::RecoveredContentKey::confirmed(
538            core::mem::take(&mut *key),
539        )])
540    }
541}
542
543/// Direct non-exportable content key, with no private-key or symmetric-byte export.
544pub struct OpaqueContentKeyResolver {
545    key: std::sync::Arc<dyn crate::provider::ContentDecryptionKey>,
546}
547
548impl OpaqueContentKeyResolver {
549    /// Retain a caller-selected handle; policy remains in the operation context.
550    pub fn new(key: std::sync::Arc<dyn crate::provider::ContentDecryptionKey>) -> Self {
551        Self { key }
552    }
553}
554
555impl DecryptionKeyResolver for OpaqueContentKeyResolver {
556    fn resolve_key(
557        &self,
558        _provider: &dyn crate::provider::CryptoProvider,
559        _algorithm: DataEncryptionAlgorithm,
560        _encrypted_key: Option<&EncryptedKey>,
561    ) -> Result<Vec<u8>, XmlEncError> {
562        Err(crate::provider::ProviderError::KeyNotExportable.into())
563    }
564    fn resolve_content_keys_with_policy(
565        &self,
566        provider: &dyn crate::provider::CryptoProvider,
567        algorithm: DataEncryptionAlgorithm,
568        encrypted_key: Option<&EncryptedKey>,
569        policy: &crate::policy::DecryptionPolicy,
570        budget: &mut KeyCandidateBudget,
571    ) -> Result<Vec<crate::provider::RecoveredContentKey>, XmlEncError> {
572        policy.validate()?;
573        if encrypted_key.is_some() {
574            return Err(XmlEncError::KeyNotFound);
575        }
576        budget.consume(1)?;
577        provider.require_capability(crate::provider::ProviderCapability::Decrypt(algorithm))?;
578        validate_content_key_len(algorithm, self.key.key_len())?;
579        Ok(vec![crate::provider::RecoveredContentKey::opaque(
580            self.key.clone(),
581        )])
582    }
583}
584
585/// Opaque KEK recipient resolver that retains the unwrapped CEK in its provider.
586pub struct OpaqueKekDecryptor {
587    key: std::sync::Arc<dyn crate::provider::KeyUnwrappingKey>,
588}
589
590impl OpaqueKekDecryptor {
591    /// Select a caller-owned KEK without adding a second policy surface.
592    pub fn new(key: std::sync::Arc<dyn crate::provider::KeyUnwrappingKey>) -> Self {
593        Self { key }
594    }
595}
596
597impl DecryptionKeyResolver for OpaqueKekDecryptor {
598    fn resolve_key(
599        &self,
600        _provider: &dyn crate::provider::CryptoProvider,
601        _algorithm: DataEncryptionAlgorithm,
602        _encrypted_key: Option<&EncryptedKey>,
603    ) -> Result<Vec<u8>, XmlEncError> {
604        Err(crate::provider::ProviderError::KeyNotExportable.into())
605    }
606    fn resolve_content_keys_with_policy(
607        &self,
608        provider: &dyn crate::provider::CryptoProvider,
609        algorithm: DataEncryptionAlgorithm,
610        encrypted_key: Option<&EncryptedKey>,
611        policy: &crate::policy::DecryptionPolicy,
612        budget: &mut KeyCandidateBudget,
613    ) -> Result<Vec<crate::provider::RecoveredContentKey>, XmlEncError> {
614        policy.validate()?;
615        let encrypted_key = encrypted_key.ok_or(XmlEncError::KeyNotFound)?;
616        validate_encrypted_key_policy(encrypted_key, policy)?;
617        encrypted_key.encryption_method.validate_structure()?;
618        let wrap = KeyWrapAlgorithm::from_uri(&encrypted_key.encryption_method.algorithm)?;
619        provider.require_capability(crate::provider::ProviderCapability::KeyUnwrap(wrap))?;
620        budget.consume(1)?;
621        let wrapped = encrypted_key.cipher_data.octets()?;
622        let expected = wrap
623            .wrapped_len(algorithm.key_len())
624            .ok_or_else(|| XmlEncError::UnsupportedAlgorithm(wrap.uri().into()))?;
625        if wrapped.len() != expected {
626            return Err(XmlEncError::InvalidWrappedKeyLength {
627                expected,
628                actual: wrapped.len(),
629            });
630        }
631        let key = provider.unwrap_content_key(self.key.as_ref(), wrap, algorithm, &wrapped)?;
632        validate_content_key_len(algorithm, key.key_len())?;
633        Ok(vec![key])
634    }
635}
636
637/// Caller-owned target selection for document decryption.
638#[derive(Debug, Clone, Copy, Default)]
639pub struct DocumentDecryptionOptions<'a> {
640    /// Select a specific `EncryptedData` by its `Id` attribute.
641    pub encrypted_data_id: Option<&'a str>,
642}
643
644/// Immutable XMLEnc decryption operation context.
645pub struct DecryptContext<'a> {
646    resolver: &'a dyn DecryptionKeyResolver,
647    policy: crate::policy::DecryptionPolicy,
648    provider: &'a dyn crate::provider::CryptoProvider,
649    xml_backend: crate::XmlBackend,
650    id_attributes: &'a [crate::IdAttributeRegistration],
651    external_resources: Option<&'a std::collections::HashMap<String, Vec<u8>>>,
652}
653
654struct DecryptionPlanNodes {
655    document: OperationNodeId,
656    ciphertext: OperationNodeId,
657    key: OperationNodeId,
658    crypto: OperationNodeId,
659    evidence: OperationNodeId,
660    mutation: Option<OperationNodeId>,
661}
662
663struct DecryptionOperationBudgets {
664    key_candidates: std::cell::RefCell<KeyCandidateBudget>,
665    xml_parse: XmlParseWorkBudget,
666}
667
668struct DecryptionReferenceGate<'a> {
669    operation:
670        &'a OperationExecutionContext<crate::policy::DecryptionPolicy, DecryptionOperationBudgets>,
671    document: OperationNodeId,
672    ciphertext: OperationNodeId,
673}
674
675impl super::cipher_reference::ReferenceOperationGate for DecryptionReferenceGate<'_> {
676    fn run_resource(
677        &self,
678        identity: &crate::operation::OperationResourceIdentity,
679        action: &mut dyn FnMut() -> Result<Vec<u8>, XmlEncError>,
680    ) -> Result<Vec<u8>, XmlEncError> {
681        self.operation
682            .run_discovered_resource(self.document, self.ciphertext, identity, action)
683    }
684}
685
686impl DecryptionOperationBudgets {
687    fn from_policy(policy: &crate::policy::DecryptionPolicy) -> Self {
688        Self {
689            key_candidates: std::cell::RefCell::new(KeyCandidateBudget::with_limit(
690                policy.resources.max_key_candidates,
691            )),
692            xml_parse: XmlParseWorkBudget::from_resources(&policy.resources),
693        }
694    }
695}
696
697fn compile_decryption_plan(
698    operation: &mut OperationExecutionContext<
699        crate::policy::DecryptionPolicy,
700        DecryptionOperationBudgets,
701    >,
702    mutation: bool,
703) -> Result<DecryptionPlanNodes, XmlEncError> {
704    let document = operation.add_node(OperationNodeKind::Document, OperationStage::Parse, None);
705    let ciphertext =
706        operation.add_node(OperationNodeKind::Ciphertext, OperationStage::Resolve, None);
707    operation
708        .add_dependency(ciphertext, document)
709        .map_err(map_decryption_plan_error)?;
710    let key = operation.add_node(
711        OperationNodeKind::Key { index: 0 },
712        OperationStage::Resolve,
713        None,
714    );
715    operation
716        .add_dependency(key, ciphertext)
717        .map_err(map_decryption_plan_error)?;
718    let crypto = operation.add_node(OperationNodeKind::Crypto, OperationStage::Crypto, None);
719    operation
720        .add_dependency(crypto, key)
721        .map_err(map_decryption_plan_error)?;
722    let evidence = operation.add_node(OperationNodeKind::Evidence, OperationStage::Evidence, None);
723    operation
724        .add_dependency(evidence, crypto)
725        .map_err(map_decryption_plan_error)?;
726    let mutation = mutation.then(|| {
727        let node = operation.add_node(OperationNodeKind::Mutation, OperationStage::Mutation, None);
728        operation
729            .add_dependency(node, evidence)
730            .expect("evidence-to-mutation stage order is fixed");
731        node
732    });
733    operation.compile().map_err(map_decryption_plan_error)?;
734    Ok(DecryptionPlanNodes {
735        document,
736        ciphertext,
737        key,
738        crypto,
739        evidence,
740        mutation,
741    })
742}
743
744struct ProcessedDecryption<T> {
745    output: T,
746    operation:
747        OperationExecutionContext<crate::policy::DecryptionPolicy, DecryptionOperationBudgets>,
748    mutation: Option<OperationNodeId>,
749}
750
751#[derive(Clone, Copy)]
752enum DecryptionInput<'a> {
753    Xml {
754        source: &'a str,
755        node_start: Option<usize>,
756    },
757    Parsed(&'a EncryptedData),
758}
759
760fn cipher_reference_node<'doc, 'input>(
761    node: crate::xml::dom::Node<'doc, 'input>,
762) -> Result<crate::xml::dom::Node<'doc, 'input>, XmlEncError> {
763    node.children()
764        .find(|child| child.has_tag_name((XMLENC_NS, "CipherData")))
765        .and_then(|child| {
766            child
767                .children()
768                .find(|node| node.has_tag_name((XMLENC_NS, "CipherReference")))
769        })
770        .ok_or(XmlEncError::MissingRequired("CipherReference"))
771}
772
773fn selected_encrypted_node<'doc, 'input>(
774    document: &'doc crate::xml::dom::Document<'input>,
775    start: Option<usize>,
776) -> Result<crate::xml::dom::Node<'doc, 'input>, XmlEncError> {
777    match start {
778        Some(start) => document
779            .descendants()
780            .find(|node| node.is_element() && node.range().start == start)
781            .ok_or_else(|| XmlEncError::OperationPlan("selected encrypted node is absent".into())),
782        None => Ok(document.root_element()),
783    }
784}
785
786fn map_decryption_plan_error(error: OperationPlanError) -> XmlEncError {
787    XmlEncError::from(error)
788}
789
790impl<'a> DecryptContext<'a> {
791    /// Create a context with the default decryption policy and RustCrypto provider.
792    pub fn new(resolver: &'a dyn DecryptionKeyResolver) -> Self {
793        Self {
794            resolver,
795            policy: crate::policy::DecryptionPolicy::default(),
796            provider: crate::provider::default_provider(),
797            xml_backend: crate::XmlBackend::default(),
798            id_attributes: &[],
799            external_resources: None,
800        }
801    }
802
803    /// Replace the complete immutable decryption policy snapshot.
804    pub fn policy(mut self, policy: crate::policy::DecryptionPolicy) -> Self {
805        self.policy = policy;
806        self
807    }
808
809    /// Select the cryptographic provider for this decryption operation.
810    pub fn provider(mut self, provider: &'a dyn crate::provider::CryptoProvider) -> Self {
811        self.provider = provider;
812        self
813    }
814
815    /// Select the compiled XML parser backend for this decryption operation.
816    pub fn xml_backend(mut self, backend: crate::XmlBackend) -> Self {
817        self.xml_backend = backend;
818        self
819    }
820
821    fn document_parse_settings(&self) -> DocumentParseSettings {
822        DocumentParseSettings::from_policy(&self.policy.xml, &self.policy.resources)
823            .with_backend(self.xml_backend)
824    }
825
826    /// Add caller-declared ID attributes for operation start-node lookup.
827    pub fn id_attributes(mut self, registrations: &'a [crate::IdAttributeRegistration]) -> Self {
828        self.id_attributes = registrations;
829        self
830    }
831
832    /// Supply immutable external ciphertext resources. No implicit I/O occurs.
833    pub fn external_resources(
834        mut self,
835        resources: &'a std::collections::HashMap<String, Vec<u8>>,
836    ) -> Self {
837        self.external_resources = Some(resources);
838        self
839    }
840
841    /// Parse and decrypt a standalone `EncryptedData` XML fragment.
842    pub fn decrypt(&self, xml: &str) -> Result<DecryptedContent, XmlEncError> {
843        let budgets = DecryptionOperationBudgets::from_policy(&self.policy);
844        self.process_decryption_input(
845            DecryptionInput::Xml {
846                source: xml,
847                node_start: None,
848            },
849            None,
850            false,
851            budgets,
852            |content, _| Ok(content),
853        )
854        .map(|processed| processed.output)
855    }
856
857    /// Decrypt an already parsed `EncryptedData` value.
858    pub fn decrypt_data(&self, encrypted: &EncryptedData) -> Result<DecryptedContent, XmlEncError> {
859        self.process_decryption_candidates(
860            encrypted,
861            None,
862            false,
863            DecryptionOperationBudgets::from_policy(&self.policy),
864            |content, _| Ok(content),
865        )
866        .map(|processed| processed.output)
867    }
868
869    fn process_decryption_candidates<T>(
870        &self,
871        encrypted: &EncryptedData,
872        document_binding: Option<(crate::DocumentIdentity, u64)>,
873        mutates_document: bool,
874        budgets: DecryptionOperationBudgets,
875        accept: impl FnMut(DecryptedContent, &XmlParseWorkBudget) -> Result<T, XmlEncError>,
876    ) -> Result<ProcessedDecryption<T>, XmlEncError> {
877        self.process_decryption_input(
878            DecryptionInput::Parsed(encrypted),
879            document_binding,
880            mutates_document,
881            budgets,
882            accept,
883        )
884    }
885
886    fn process_decryption_input<T>(
887        &self,
888        input: DecryptionInput<'_>,
889        document_binding: Option<(crate::DocumentIdentity, u64)>,
890        mutates_document: bool,
891        budgets: DecryptionOperationBudgets,
892        mut accept: impl FnMut(DecryptedContent, &XmlParseWorkBudget) -> Result<T, XmlEncError>,
893    ) -> Result<ProcessedDecryption<T>, XmlEncError> {
894        let mut operation =
895            OperationExecutionContext::new(self.policy.clone(), budgets, document_binding);
896        operation.policy().resources.validate()?;
897        let plan_nodes = compile_decryption_plan(&mut operation, mutates_document)?;
898        let source_document = operation.run(plan_nodes.document, || match input {
899            DecryptionInput::Xml { source, node_start } => {
900                let settings = self.document_parse_settings();
901                let document = crate::document::parse_borrowed_with_settings_and_budget(
902                    source,
903                    settings,
904                    Some(&operation.budgets().xml_parse),
905                )
906                .map_err(|error| map_document_error(error, settings))?;
907                Ok::<_, XmlEncError>(Some((document, node_start)))
908            }
909            DecryptionInput::Parsed(_) => Ok(None),
910        })?;
911        let reference_gate = DecryptionReferenceGate {
912            operation: &operation,
913            document: plan_nodes.document,
914            ciphertext: plan_nodes.ciphertext,
915        };
916        let reference_context = super::CipherReferenceContext::new(
917            operation.policy(),
918            self.external_resources,
919            self.xml_backend,
920            self.id_attributes,
921        )?
922        .with_operation(&reference_gate);
923        let parsed = operation.run(plan_nodes.ciphertext, || {
924            // The graph gates parser work as well as cryptographic callbacks.
925            // Typed inputs stay borrowed; XML inputs consume the same operation
926            // parse allowance used later for plaintext and controlled mutation.
927            let (mut encrypted, origins) = match input {
928                DecryptionInput::Xml { .. } => {
929                    let (document, node_start) =
930                        source_document.as_ref().expect("XML input parsed once");
931                    let selected = match *node_start {
932                        Some(start) => document
933                            .descendants()
934                            .find(|node| node.is_element() && node.range().start == start)
935                            .ok_or_else(|| {
936                                XmlEncError::OperationPlan(
937                                    "selected encrypted node changed during policy parsing".into(),
938                                )
939                            })?,
940                        None => document.root_element(),
941                    };
942                    let (encrypted, origins) =
943                        super::parse::parse_encrypted_data_node_with_origins(
944                            selected,
945                            operation.policy().into(),
946                            false,
947                            self.id_attributes,
948                            self.provider,
949                            Some(&reference_context),
950                            Some(&operation.budgets().xml_parse),
951                        )?;
952                    (Cow::Owned(encrypted), origins)
953                }
954                DecryptionInput::Parsed(encrypted) => (Cow::Borrowed(encrypted), Vec::new()),
955            };
956            validate_encrypted_data_metadata(&encrypted, operation.policy())?;
957            encrypted.encryption_method.validate_structure()?;
958            validate_recipient_count(
959                encrypted.encrypted_keys.len(),
960                operation.policy().resources.max_encryption_recipients,
961            )?;
962            let algorithm =
963                DataEncryptionAlgorithm::from_uri(&encrypted.encryption_method.algorithm)?;
964            crate::policy::check_content_algorithm(
965                operation.policy().data_algorithms.as_ref(),
966                algorithm,
967                "decryption",
968            )?;
969            self.provider
970                .require_capability(crate::provider::ProviderCapability::Decrypt(algorithm))?;
971            validate_typed_cipher_values(
972                &encrypted,
973                algorithm,
974                operation.policy().resources.max_encryption_plaintext_bytes,
975                operation.policy().resources.max_xml_document_bytes,
976            )?;
977            if matches!(
978                algorithm,
979                DataEncryptionAlgorithm::ChaCha20 | DataEncryptionAlgorithm::ChaCha20Poly1305
980            ) && encrypted
981                .encryption_method
982                .chacha
983                .as_ref()
984                .and_then(|parameters| parameters.nonce)
985                .is_none()
986            {
987                return Err(XmlEncError::MissingRequired("ChaCha Nonce"));
988            }
989            let bound_references = source_document
990                .as_ref()
991                .map(|(document, _)| reference_context.bind_document(document));
992            let ciphertext = match (&encrypted.cipher_data, source_document.as_ref()) {
993                (super::CipherData::Reference { uri, transforms }, Some((document, start))) => {
994                    let source = selected_encrypted_node(document, *start)?;
995                    bound_references
996                        .as_ref()
997                        .expect("XML source has a bound reference context")
998                        .resolve_parsed(
999                            cipher_reference_node(source)?,
1000                            uri,
1001                            transforms,
1002                            &operation.budgets().xml_parse,
1003                        )?
1004                }
1005                _ => encrypted.cipher_data.octets()?.into_owned(),
1006            };
1007            if let Some((document, _)) = source_document.as_ref() {
1008                let mut origins = origins.iter();
1009                resolve_nested_cipher_references(
1010                    &mut encrypted.to_mut().encrypted_keys,
1011                    document,
1012                    &mut origins,
1013                    bound_references
1014                        .as_ref()
1015                        .expect("XML source has a bound reference context"),
1016                    &operation.budgets().xml_parse,
1017                )?;
1018                if origins.next().is_some() {
1019                    return Err(XmlEncError::OperationPlan(
1020                        "unused encrypted key origin".into(),
1021                    ));
1022                }
1023            }
1024            validate_typed_cipher_values(
1025                &encrypted,
1026                algorithm,
1027                operation.policy().resources.max_encryption_plaintext_bytes,
1028                operation.policy().resources.max_xml_document_bytes,
1029            )?;
1030            validate_content_framing_before_resolution(
1031                algorithm,
1032                ciphertext.len(),
1033                &encrypted.encrypted_keys,
1034                operation.policy(),
1035            )?;
1036            validate_possible_plaintext_len(
1037                algorithm,
1038                ciphertext.len(),
1039                operation.policy().resources.max_encryption_plaintext_bytes,
1040            )?;
1041            Ok::<_, XmlEncError>((encrypted, algorithm, ciphertext, origins, bound_references))
1042        });
1043        let (encrypted, algorithm, ciphertext, origins, bound_references) = parsed?;
1044        let keys = operation.run(plan_nodes.key, || {
1045            let document = match (source_document.as_ref(), bound_references.as_ref()) {
1046                (Some((document, start)), Some(bound)) => Some(KeySourceDocument {
1047                    references: bound,
1048                    target: selected_encrypted_node(document, *start)?.id(),
1049                    origins: &origins,
1050                }),
1051                _ => None,
1052            };
1053            resolve_content_key_candidates(
1054                self.provider,
1055                algorithm,
1056                &encrypted,
1057                self.resolver,
1058                operation.policy(),
1059                &mut operation.budgets().key_candidates.borrow_mut(),
1060                document,
1061            )
1062        })?;
1063        let keys = compatible_decryption_key_candidates(algorithm, keys)?;
1064        validate_decryption_key_candidates(algorithm, keys.len())?;
1065        let output = operation.run(plan_nodes.crypto, || {
1066            let mut last_error = None;
1067            for key in keys {
1068                let attempt = (|| {
1069                    validate_content_key_len(algorithm, key.key_len())?;
1070                    let result = key.decrypt(
1071                        self.provider,
1072                        algorithm,
1073                        &ciphertext,
1074                        encrypted
1075                            .encryption_method
1076                            .chacha
1077                            .as_ref()
1078                            .map(super::ChaChaParameters::borrowed),
1079                    );
1080                    // XMLEnc 1.1 §6.1.2: fallback recovery must still perform
1081                    // content work. Never release a CBC result merely because
1082                    // its unauthenticated padding happened to be valid.
1083                    // https://www.w3.org/TR/2013/REC-xmlenc-core1-20130411/#sec-bleichenbacher-attack
1084                    if !key.valid() {
1085                        if let Ok(mut plaintext) = result {
1086                            zeroize::Zeroize::zeroize(&mut plaintext);
1087                        }
1088                        return Err(if algorithm.cbc_block_len().is_some() {
1089                            XmlEncError::InvalidPadding
1090                        } else {
1091                            XmlEncError::AeadAuthenticationFailed
1092                        });
1093                    }
1094                    let plaintext = result.map_err(|error| {
1095                        map_data_decryption_error(algorithm, ciphertext.len(), error)
1096                    })?;
1097                    validate_provider_plaintext_len(algorithm, ciphertext.len(), plaintext.len())?;
1098                    validate_plaintext_len(
1099                        plaintext.len(),
1100                        operation.policy().resources.max_encryption_plaintext_bytes,
1101                    )?;
1102                    let content = match encrypted.encrypted_type.as_ref() {
1103                        Some(EncryptedDataType::Element | EncryptedDataType::Content) => {
1104                            DecryptedContent::Xml(String::from_utf8(plaintext)?)
1105                        }
1106                        Some(EncryptedDataType::Other(_)) | None => {
1107                            DecryptedContent::Bytes(plaintext)
1108                        }
1109                    };
1110                    accept(content, &operation.budgets().xml_parse)
1111                })();
1112                match attempt {
1113                    Ok(output) => return Ok(output),
1114                    Err(error) => last_error = Some(error),
1115                }
1116            }
1117            Err(last_error.unwrap_or(XmlEncError::KeyNotFound))
1118        })?;
1119        let output = operation.run(plan_nodes.evidence, || Ok::<_, XmlEncError>(output))?;
1120        Ok(ProcessedDecryption {
1121            output,
1122            operation,
1123            mutation: plan_nodes.mutation,
1124        })
1125    }
1126
1127    /// Decrypt and replace one selected `EncryptedData` in a caller-owned document.
1128    pub fn decrypt_document(
1129        &self,
1130        xml: &str,
1131        encrypted_data_id: Option<&str>,
1132    ) -> Result<String, XmlEncError> {
1133        decrypt_document_with_context(
1134            xml,
1135            DocumentEncryptedDataSelector::EncryptedDataId(encrypted_data_id),
1136            self,
1137        )
1138    }
1139
1140    /// Decrypt and replace one selected `EncryptedData` in an owned document.
1141    pub fn decrypt_owned_document(
1142        &self,
1143        document: &mut XmlDocument,
1144        encrypted_data_id: Option<&str>,
1145    ) -> Result<(), XmlEncError> {
1146        let budgets = DecryptionOperationBudgets::from_policy(&self.policy);
1147        decrypt_owned_document_with_context(
1148            document,
1149            DocumentEncryptedDataSelector::EncryptedDataId(encrypted_data_id),
1150            self,
1151            budgets,
1152        )
1153    }
1154
1155    /// Decrypt and replace the sole `EncryptedData` below an operation start
1156    /// node selected by ID.
1157    pub fn decrypt_document_from_start_node(
1158        &self,
1159        xml: &str,
1160        start_node_id: Option<&str>,
1161    ) -> Result<String, XmlEncError> {
1162        decrypt_document_with_context(
1163            xml,
1164            DocumentEncryptedDataSelector::UniqueBelowStartNode(start_node_id),
1165            self,
1166        )
1167    }
1168
1169    /// Decrypt and replace the first `EncryptedData` below an operation start
1170    /// node selected by ID, leaving later encrypted descendants untouched.
1171    pub fn decrypt_first_document_from_start_node(
1172        &self,
1173        xml: &str,
1174        start_node_id: Option<&str>,
1175    ) -> Result<String, XmlEncError> {
1176        decrypt_document_with_context(
1177            xml,
1178            DocumentEncryptedDataSelector::FirstBelowStartNode(start_node_id),
1179            self,
1180        )
1181    }
1182}
1183
1184/// Resolver for direct, pre-shared AES content keys.
1185#[derive(Clone)]
1186pub struct SymmetricKeyDecryptor {
1187    key: Vec<u8>,
1188    kind: crate::key_manager::SymmetricKeyKind,
1189}
1190
1191impl fmt::Debug for SymmetricKeyDecryptor {
1192    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1193        formatter
1194            .debug_struct("SymmetricKeyDecryptor")
1195            .field("key", &"[REDACTED]")
1196            .finish()
1197    }
1198}
1199
1200impl SymmetricKeyDecryptor {
1201    /// Create a direct AES-key resolver. Key length never selects another family.
1202    pub fn new(key: impl Into<Vec<u8>>) -> Self {
1203        Self::with_kind(key, crate::key_manager::SymmetricKeyKind::Aes)
1204    }
1205
1206    /// Bind a pre-shared key to its trusted family before reading an algorithm
1207    /// from the document. XMLEnc 1.1 §6.1.3 recommends key separation:
1208    /// https://www.w3.org/TR/2013/REC-xmlenc-core1-20130411/#sec-backwards-compatibility-attacks
1209    pub fn with_kind(key: impl Into<Vec<u8>>, kind: crate::key_manager::SymmetricKeyKind) -> Self {
1210        Self {
1211            key: key.into(),
1212            kind,
1213        }
1214    }
1215}
1216
1217impl DecryptionKeyResolver for SymmetricKeyDecryptor {
1218    fn resolve_content_keys_with_policy(
1219        &self,
1220        provider: &dyn crate::provider::CryptoProvider,
1221        algorithm: DataEncryptionAlgorithm,
1222        encrypted_key: Option<&EncryptedKey>,
1223        policy: &crate::policy::DecryptionPolicy,
1224        budget: &mut KeyCandidateBudget,
1225    ) -> Result<Vec<crate::provider::RecoveredContentKey>, XmlEncError> {
1226        policy.validate()?;
1227        if encrypted_key.is_some() {
1228            return Err(XmlEncError::KeyNotFound);
1229        }
1230        budget.consume(1)?;
1231        Ok(vec![crate::provider::RecoveredContentKey::confirmed(
1232            self.resolve_key(provider, algorithm, None)?,
1233        )])
1234    }
1235
1236    fn resolve_key(
1237        &self,
1238        _provider: &dyn crate::provider::CryptoProvider,
1239        algorithm: DataEncryptionAlgorithm,
1240        _encrypted_key: Option<&EncryptedKey>,
1241    ) -> Result<Vec<u8>, XmlEncError> {
1242        if self.kind != algorithm.key_kind() {
1243            return Err(XmlEncError::KeyNotFound);
1244        }
1245        validate_key_len(algorithm, &self.key)?;
1246        Ok(self.key.clone())
1247    }
1248}
1249
1250/// Resolver backed by an RSA private key for OAEP-wrapped session keys.
1251#[derive(Clone)]
1252pub struct PrivateKeyDecryptor {
1253    key: Arc<dyn crate::provider::KeyRecoveryKey>,
1254}
1255
1256/// Resolver backed by a pre-shared, algorithm-family-bound key-encryption key.
1257#[derive(Clone)]
1258pub struct KekDecryptor<'a> {
1259    kek: std::borrow::Cow<'a, [u8]>,
1260    kind: crate::key_manager::SymmetricKeyKind,
1261}
1262
1263impl fmt::Debug for KekDecryptor<'_> {
1264    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1265        formatter
1266            .debug_struct("KekDecryptor")
1267            .field("kek", &"[REDACTED]")
1268            .field("kind", &self.kind)
1269            .finish()
1270    }
1271}
1272
1273impl KekDecryptor<'static> {
1274    /// Create a resolver for RFC 3394 AES key-wrap `EncryptedKey` elements.
1275    pub fn new(kek: impl Into<Vec<u8>>) -> Self {
1276        Self::with_kind(kek, crate::key_manager::SymmetricKeyKind::Aes)
1277    }
1278
1279    /// Bind owned key material to its trusted family, independently of input XML.
1280    pub fn with_kind(kek: impl Into<Vec<u8>>, kind: crate::key_manager::SymmetricKeyKind) -> Self {
1281        Self {
1282            kek: std::borrow::Cow::Owned(kek.into()),
1283            kind,
1284        }
1285    }
1286}
1287
1288impl<'a> KekDecryptor<'a> {
1289    /// Borrow a KEK for operation-scoped recovery without copying secret material.
1290    pub fn borrowed(kek: &'a [u8]) -> Self {
1291        Self::borrowed_with_kind(kek, crate::key_manager::SymmetricKeyKind::Aes)
1292    }
1293
1294    /// Borrow key material while retaining its caller-declared algorithm family.
1295    pub fn borrowed_with_kind(kek: &'a [u8], kind: crate::key_manager::SymmetricKeyKind) -> Self {
1296        Self {
1297            kek: std::borrow::Cow::Borrowed(kek),
1298            kind,
1299        }
1300    }
1301}
1302
1303impl DecryptionKeyResolver for KekDecryptor<'_> {
1304    fn resolve_key_encryption_keys_with_policy(
1305        &self,
1306        provider: &dyn crate::provider::CryptoProvider,
1307        algorithm: KeyWrapAlgorithm,
1308        source: KeyEncryptionKeySource<'_>,
1309        policy: &crate::policy::DecryptionPolicy,
1310        budget: &mut KeyCandidateBudget,
1311    ) -> Result<Vec<crate::provider::RecoveredContentKey>, XmlEncError> {
1312        match source {
1313            KeyEncryptionKeySource::Direct => {
1314                if self.kind != algorithm.key_kind() || self.kek.len() != algorithm.key_len() {
1315                    return Err(XmlEncError::KeyNotFound);
1316                }
1317                budget.consume(1)?;
1318                Ok(vec![crate::provider::RecoveredContentKey::confirmed(
1319                    self.kek.to_vec(),
1320                )])
1321            }
1322            KeyEncryptionKeySource::Encrypted(key) => {
1323                validate_encrypted_key_policy(key, policy)?;
1324                budget.consume(2)?;
1325                Ok(vec![crate::provider::RecoveredContentKey::confirmed(
1326                    self.unwrap(provider, algorithm.key_len(), key)?,
1327                )])
1328            }
1329            _ => Err(XmlEncError::KeyNotFound),
1330        }
1331    }
1332    fn resolve_content_keys_with_policy(
1333        &self,
1334        provider: &dyn crate::provider::CryptoProvider,
1335        algorithm: DataEncryptionAlgorithm,
1336        encrypted_key: Option<&EncryptedKey>,
1337        policy: &crate::policy::DecryptionPolicy,
1338        budget: &mut KeyCandidateBudget,
1339    ) -> Result<Vec<crate::provider::RecoveredContentKey>, XmlEncError> {
1340        policy.validate()?;
1341        let encrypted_key = encrypted_key.ok_or(XmlEncError::KeyNotFound)?;
1342        validate_encrypted_key_policy(encrypted_key, policy)?;
1343        budget.consume(1)?;
1344        Ok(vec![crate::provider::RecoveredContentKey::confirmed(
1345            self.resolve_key(provider, algorithm, Some(encrypted_key))?,
1346        )])
1347    }
1348
1349    fn resolve_key_candidates_with_policy(
1350        &self,
1351        provider: &dyn crate::provider::CryptoProvider,
1352        algorithm: DataEncryptionAlgorithm,
1353        encrypted_key: Option<&EncryptedKey>,
1354        policy: &crate::policy::DecryptionPolicy,
1355        budget: &mut KeyCandidateBudget,
1356    ) -> Result<Vec<Vec<u8>>, XmlEncError> {
1357        self.resolve_content_keys_with_policy(provider, algorithm, encrypted_key, policy, budget)?
1358            .into_iter()
1359            .map(|key| key.into_key().map_err(XmlEncError::from))
1360            .collect()
1361    }
1362
1363    fn resolve_key(
1364        &self,
1365        provider: &dyn crate::provider::CryptoProvider,
1366        algorithm: DataEncryptionAlgorithm,
1367        encrypted_key: Option<&EncryptedKey>,
1368    ) -> Result<Vec<u8>, XmlEncError> {
1369        let encrypted_key = encrypted_key.ok_or(XmlEncError::KeyNotFound)?;
1370        let key = self.unwrap(provider, algorithm.key_len(), encrypted_key)?;
1371        validate_key_len(algorithm, &key)?;
1372        Ok(key)
1373    }
1374}
1375
1376impl KekDecryptor<'_> {
1377    fn unwrap(
1378        &self,
1379        provider: &dyn crate::provider::CryptoProvider,
1380        output_len: usize,
1381        encrypted_key: &EncryptedKey,
1382    ) -> Result<Vec<u8>, XmlEncError> {
1383        encrypted_key.encryption_method.validate_structure()?;
1384        let wrap_algorithm =
1385            KeyWrapAlgorithm::from_uri(&encrypted_key.encryption_method.algorithm)?;
1386        // XMLEnc 1.1 section 6.1.3 recommends key separation between algorithms:
1387        // https://www.w3.org/TR/2013/REC-xmlenc-core1-20130411/#sec-backwards-compatibility-attacks
1388        // Equal AES-192 and TripleDES widths do not establish key identity.
1389        if self.kind != wrap_algorithm.key_kind() {
1390            return Err(XmlEncError::KeyNotFound);
1391        }
1392        let wrapped = encrypted_key.cipher_data.octets()?;
1393        let expected_kek_len = wrap_algorithm.key_len();
1394        if self.kek.len() != expected_kek_len {
1395            return Err(XmlEncError::InvalidKekSize {
1396                algorithm: wrap_algorithm,
1397                expected: expected_kek_len,
1398                actual: self.kek.len(),
1399            });
1400        }
1401        let expected_wrapped_len = wrap_algorithm
1402            .wrapped_len(output_len)
1403            .ok_or_else(|| XmlEncError::UnsupportedAlgorithm(wrap_algorithm.uri().into()))?;
1404        if wrapped.len() != expected_wrapped_len {
1405            return Err(XmlEncError::InvalidWrappedKeyLength {
1406                expected: expected_wrapped_len,
1407                actual: wrapped.len(),
1408            });
1409        }
1410        provider.require_capability(crate::provider::ProviderCapability::KeyUnwrap(
1411            wrap_algorithm,
1412        ))?;
1413        let key = provider
1414            .unwrap_key(wrap_algorithm, &self.kek, &wrapped)
1415            .map_err(|error| match error {
1416                crate::provider::ProviderError::InvalidKeySize { expected, actual } => {
1417                    XmlEncError::InvalidKekSize {
1418                        algorithm: wrap_algorithm,
1419                        expected,
1420                        actual,
1421                    }
1422                }
1423                crate::provider::ProviderError::AuthenticationFailed
1424                | crate::provider::ProviderError::InvalidInput(
1425                    crate::provider::ProviderInputError::AesKeyWrapFraming,
1426                ) => XmlEncError::KeyWrapIntegrity,
1427                error => XmlEncError::Provider(error),
1428            })?;
1429        if key.len() != output_len {
1430            return Err(XmlEncError::InvalidWrappedKeyLength {
1431                expected: output_len,
1432                actual: key.len(),
1433            });
1434        }
1435        Ok(key)
1436    }
1437}
1438
1439impl PrivateKeyDecryptor {
1440    /// Create a resolver from an already-parsed RSA private key.
1441    pub fn new(key: RsaPrivateKey) -> Self {
1442        Self::provider_key(Arc::new(crate::provider::RustCryptoRsaPrivateKey::new(key)))
1443    }
1444
1445    /// Create a resolver from an opaque provider-owned recovery key.
1446    pub fn provider_key(key: Arc<dyn crate::provider::KeyRecoveryKey>) -> Self {
1447        Self { key }
1448    }
1449}
1450
1451impl DecryptionKeyResolver for PrivateKeyDecryptor {
1452    fn resolve_key_encryption_keys_with_policy(
1453        &self,
1454        provider: &dyn crate::provider::CryptoProvider,
1455        algorithm: KeyWrapAlgorithm,
1456        source: KeyEncryptionKeySource<'_>,
1457        policy: &crate::policy::DecryptionPolicy,
1458        budget: &mut KeyCandidateBudget,
1459    ) -> Result<Vec<crate::provider::RecoveredContentKey>, XmlEncError> {
1460        let KeyEncryptionKeySource::Encrypted(key) = source else {
1461            return Err(XmlEncError::KeyNotFound);
1462        };
1463        validate_encrypted_key_policy(key, policy)?;
1464        let width = policy.rsa_keys.validate_public_metadata(
1465            "decryption",
1466            self.key.rsa_modulus_bits(),
1467            self.key.rsa_public_exponent(),
1468        )?;
1469        let ciphertext = key.cipher_data.octets()?;
1470        if width != self.key.ciphertext_len() || ciphertext.len() != width {
1471            return Err(XmlEncError::InvalidWrappedKeyLength {
1472                expected: width,
1473                actual: ciphertext.len(),
1474            });
1475        }
1476        let transport = KeyTransportAlgorithm::from_uri(&key.encryption_method.algorithm)?;
1477        budget.consume(2)?;
1478        #[cfg(feature = "legacy-algorithms")]
1479        if transport == KeyTransportAlgorithm::RsaPkcs1v15 {
1480            provider.require_capability(crate::provider::ProviderCapability::Pkcs1v15Recovery)?;
1481            return Ok(vec![provider.recover_pkcs1v15(
1482                self.key.as_ref(),
1483                &ciphertext,
1484                algorithm.key_len(),
1485            )?]);
1486        }
1487        let method = &key.encryption_method;
1488        let parameters = RsaOaepParameters {
1489            algorithm: transport,
1490            digest: parse_oaep_digest(method.oaep_digest.as_deref())?,
1491            mgf_digest: parse_oaep_mgf_digest(method.mgf_algorithm.as_deref())?,
1492            label: method.oaep_params.clone().unwrap_or_default(),
1493        };
1494        provider.require_capability(crate::provider::ProviderCapability::KeyRecovery(
1495            &parameters,
1496        ))?;
1497        let key = zeroize::Zeroizing::new(provider.recover_key(
1498            self.key.as_ref(),
1499            &parameters,
1500            &ciphertext,
1501        )?);
1502        if key.len() != algorithm.key_len() {
1503            return Err(XmlEncError::InvalidKekSize {
1504                algorithm,
1505                expected: algorithm.key_len(),
1506                actual: key.len(),
1507            });
1508        }
1509        let mut key = key;
1510        Ok(vec![crate::provider::RecoveredContentKey::confirmed(
1511            core::mem::take(&mut *key),
1512        )])
1513    }
1514    fn resolve_content_keys_with_policy(
1515        &self,
1516        provider: &dyn crate::provider::CryptoProvider,
1517        algorithm: DataEncryptionAlgorithm,
1518        encrypted_key: Option<&EncryptedKey>,
1519        policy: &crate::policy::DecryptionPolicy,
1520        budget: &mut KeyCandidateBudget,
1521    ) -> Result<Vec<crate::provider::RecoveredContentKey>, XmlEncError> {
1522        policy.validate()?;
1523        budget.consume(1)?;
1524        self.resolve_key_with_policy(provider, algorithm, encrypted_key, policy)
1525            .map(|key| vec![key])
1526    }
1527    fn resolve_key_candidates_with_policy(
1528        &self,
1529        provider: &dyn crate::provider::CryptoProvider,
1530        algorithm: DataEncryptionAlgorithm,
1531        encrypted_key: Option<&EncryptedKey>,
1532        policy: &crate::policy::DecryptionPolicy,
1533        budget: &mut KeyCandidateBudget,
1534    ) -> Result<Vec<Vec<u8>>, XmlEncError> {
1535        policy.validate()?;
1536        budget.consume(1)?;
1537        self.resolve_key_with_policy(provider, algorithm, encrypted_key, policy)
1538            .and_then(|key| key.into_key().map_err(XmlEncError::Provider))
1539            .map(|key| vec![key])
1540    }
1541
1542    fn resolve_key(
1543        &self,
1544        provider: &dyn crate::provider::CryptoProvider,
1545        algorithm: DataEncryptionAlgorithm,
1546        encrypted_key: Option<&EncryptedKey>,
1547    ) -> Result<Vec<u8>, XmlEncError> {
1548        self.resolve_key_with_policy(
1549            provider,
1550            algorithm,
1551            encrypted_key,
1552            &crate::policy::DecryptionPolicy::default(),
1553        )
1554        .and_then(|key| key.into_key().map_err(XmlEncError::Provider))
1555    }
1556}
1557
1558impl PrivateKeyDecryptor {
1559    /// Recover one session key using the exact operation policy. This avoids
1560    /// a temporary candidate collection when composing ordered RSA key rings.
1561    pub fn resolve_key_with_policy(
1562        &self,
1563        provider: &dyn crate::provider::CryptoProvider,
1564        algorithm: DataEncryptionAlgorithm,
1565        encrypted_key: Option<&EncryptedKey>,
1566        policy: &crate::policy::DecryptionPolicy,
1567    ) -> Result<crate::provider::RecoveredContentKey, XmlEncError> {
1568        policy.validate()?;
1569        let encrypted_key = encrypted_key.ok_or(XmlEncError::KeyNotFound)?;
1570        validate_encrypted_key_policy(encrypted_key, policy)?;
1571        let width = policy.rsa_keys.validate_public_metadata(
1572            "decryption",
1573            self.key.rsa_modulus_bits(),
1574            self.key.rsa_public_exponent(),
1575        )?;
1576        if width != self.key.ciphertext_len() {
1577            return Err(crate::policy::PolicyViolation::InvalidKeyMaterial {
1578                operation: "decryption",
1579                key_type: "RSA",
1580                reason: "ciphertext width disagrees with modulus",
1581            }
1582            .into());
1583        }
1584        encrypted_key.encryption_method.validate_structure()?;
1585        let wrapped = encrypted_key.cipher_data.octets()?;
1586        let label = encrypted_key
1587            .encryption_method
1588            .oaep_params
1589            .clone()
1590            .unwrap_or_default();
1591        let transport =
1592            KeyTransportAlgorithm::from_uri(&encrypted_key.encryption_method.algorithm)?;
1593        let key = match transport {
1594            #[cfg(feature = "legacy-algorithms")]
1595            KeyTransportAlgorithm::RsaPkcs1v15 => {
1596                if wrapped.len() != self.key.ciphertext_len() {
1597                    return Err(XmlEncError::InvalidWrappedKeyLength {
1598                        expected: self.key.ciphertext_len(),
1599                        actual: wrapped.len(),
1600                    });
1601                }
1602                provider
1603                    .require_capability(crate::provider::ProviderCapability::Pkcs1v15Recovery)?;
1604                // Recovery conceals padding/range rejection with a random CEK;
1605                // only operational provider failures propagate here. The RFC
1606                // padding-error rule is RFC 8017 §7.2.2's note:
1607                // https://www.rfc-editor.org/rfc/rfc8017#section-7.2.2.
1608                provider
1609                    .recover_pkcs1v15(self.key.as_ref(), &wrapped, algorithm.key_len())
1610                    .map_err(XmlEncError::Provider)
1611            }
1612            KeyTransportAlgorithm::RsaOaepMgf1p => self.decrypt_oaep_mgf1p(
1613                provider,
1614                encrypted_key.encryption_method.oaep_digest.as_deref(),
1615                encrypted_key.encryption_method.mgf_algorithm.as_deref(),
1616                label,
1617                &wrapped,
1618                algorithm,
1619            ),
1620            KeyTransportAlgorithm::RsaOaep11 => self.decrypt_oaep11(
1621                provider,
1622                encrypted_key.encryption_method.oaep_digest.as_deref(),
1623                encrypted_key.encryption_method.mgf_algorithm.as_deref(),
1624                label,
1625                &wrapped,
1626                algorithm,
1627            ),
1628        }?;
1629        validate_content_key_len(algorithm, key.key_len())?;
1630        Ok(key)
1631    }
1632}
1633
1634impl PrivateKeyDecryptor {
1635    fn decrypt_oaep_mgf1p(
1636        &self,
1637        provider: &dyn crate::provider::CryptoProvider,
1638        digest: Option<&str>,
1639        mgf: Option<&str>,
1640        label: Vec<u8>,
1641        wrapped: &[u8],
1642        algorithm: DataEncryptionAlgorithm,
1643    ) -> Result<crate::provider::RecoveredContentKey, XmlEncError> {
1644        let parameters = RsaOaepParameters {
1645            algorithm: KeyTransportAlgorithm::RsaOaepMgf1p,
1646            digest: parse_oaep_digest(digest)?,
1647            mgf_digest: parse_oaep_mgf_digest(mgf)?,
1648            label,
1649        };
1650        recover_rsa_oaep(provider, self.key.as_ref(), &parameters, wrapped, algorithm)
1651    }
1652
1653    fn decrypt_oaep11(
1654        &self,
1655        provider: &dyn crate::provider::CryptoProvider,
1656        digest: Option<&str>,
1657        mgf: Option<&str>,
1658        label: Vec<u8>,
1659        wrapped: &[u8],
1660        algorithm: DataEncryptionAlgorithm,
1661    ) -> Result<crate::provider::RecoveredContentKey, XmlEncError> {
1662        let parameters = RsaOaepParameters {
1663            algorithm: KeyTransportAlgorithm::RsaOaep11,
1664            digest: parse_oaep_digest(digest)?,
1665            mgf_digest: parse_oaep_mgf_digest(mgf)?,
1666            label,
1667        };
1668        recover_rsa_oaep(provider, self.key.as_ref(), &parameters, wrapped, algorithm)
1669    }
1670}
1671
1672fn parse_oaep_digest(uri: Option<&str>) -> Result<OaepDigestAlgorithm, XmlEncError> {
1673    let uri = uri.unwrap_or("http://www.w3.org/2000/09/xmldsig#sha1");
1674    OaepDigestAlgorithm::from_uri(uri)
1675        .ok_or_else(|| XmlEncError::UnsupportedAlgorithm(uri.to_owned()))
1676}
1677
1678fn parse_oaep_mgf_digest(uri: Option<&str>) -> Result<OaepDigestAlgorithm, XmlEncError> {
1679    let uri = uri.unwrap_or("http://www.w3.org/2009/xmlenc11#mgf1sha1");
1680    OaepDigestAlgorithm::from_mgf_uri(uri)
1681        .ok_or_else(|| XmlEncError::UnsupportedAlgorithm(uri.to_owned()))
1682}
1683
1684fn recover_rsa_oaep(
1685    provider: &dyn crate::provider::CryptoProvider,
1686    key: &dyn crate::provider::KeyRecoveryKey,
1687    parameters: &RsaOaepParameters,
1688    wrapped: &[u8],
1689    algorithm: DataEncryptionAlgorithm,
1690) -> Result<crate::provider::RecoveredContentKey, XmlEncError> {
1691    let expected = key.ciphertext_len();
1692    if wrapped.len() != expected {
1693        return Err(XmlEncError::InvalidWrappedKeyLength {
1694            expected,
1695            actual: wrapped.len(),
1696        });
1697    }
1698    provider.require_capability(crate::provider::ProviderCapability::KeyRecovery(parameters))?;
1699    provider
1700        .recover_content_key(key, parameters, algorithm, wrapped)
1701        .map_err(|error| match error {
1702            crate::provider::ProviderError::Random(message) => XmlEncError::Rng(message),
1703            error @ (crate::provider::ProviderError::AuthenticationFailed
1704            | crate::provider::ProviderError::InvalidInput(_)) => {
1705                XmlEncError::Rsa(error.to_string())
1706            }
1707            error => XmlEncError::Provider(error),
1708        })
1709}
1710
1711/// Parse and decrypt a standalone `EncryptedData` XML fragment.
1712pub fn decrypt(
1713    xml: &str,
1714    resolver: &dyn DecryptionKeyResolver,
1715) -> Result<DecryptedContent, XmlEncError> {
1716    DecryptContext::new(resolver).decrypt(xml)
1717}
1718
1719/// Decrypt and replace one `EncryptedData` element in a caller-owned XML document.
1720///
1721/// When `encrypted_data_id` is `None`, the document must contain exactly one
1722/// `EncryptedData`. The decrypted value must declare either the XMLEnc `Element`
1723/// or `Content` type. Plaintext is parsed inside a bounded replacement wrapper
1724/// before insertion, and the returned document is parsed again before exposure.
1725pub fn decrypt_document(
1726    xml: &str,
1727    encrypted_data_id: Option<&str>,
1728    resolver: &dyn DecryptionKeyResolver,
1729) -> Result<String, XmlEncError> {
1730    decrypt_document_with_options(
1731        xml,
1732        DocumentDecryptionOptions { encrypted_data_id },
1733        resolver,
1734    )
1735}
1736
1737/// Decrypt and replace one `EncryptedData` using the default decryption policy.
1738///
1739/// Use [`DecryptContext`] when XML-input or resource policy must differ from
1740/// the secure defaults; options here contain request selection only.
1741pub fn decrypt_document_with_options(
1742    xml: &str,
1743    options: DocumentDecryptionOptions<'_>,
1744    resolver: &dyn DecryptionKeyResolver,
1745) -> Result<String, XmlEncError> {
1746    DecryptContext::new(resolver).decrypt_document(xml, options.encrypted_data_id)
1747}
1748
1749#[derive(Clone, Copy)]
1750enum DocumentEncryptedDataSelector<'a> {
1751    EncryptedDataId(Option<&'a str>),
1752    UniqueBelowStartNode(Option<&'a str>),
1753    FirstBelowStartNode(Option<&'a str>),
1754}
1755
1756fn decrypt_document_with_context(
1757    xml: &str,
1758    selector: DocumentEncryptedDataSelector<'_>,
1759    context: &DecryptContext<'_>,
1760) -> Result<String, XmlEncError> {
1761    context.policy.resources.validate()?;
1762    validate_encryption_document_len(xml.len(), &context.policy)?;
1763    let budgets = DecryptionOperationBudgets::from_policy(&context.policy);
1764    let settings = context.document_parse_settings();
1765    let mut document =
1766        XmlDocument::parse_with_settings_and_budget(xml.to_owned(), settings, &budgets.xml_parse)
1767            .map_err(|error| map_document_error(error, settings))?;
1768    decrypt_owned_document_with_context(&mut document, selector, context, budgets)?;
1769    Ok(document.into_xml())
1770}
1771
1772fn decrypt_owned_document_with_context(
1773    document: &mut XmlDocument,
1774    selector: DocumentEncryptedDataSelector<'_>,
1775    context: &DecryptContext<'_>,
1776    budgets: DecryptionOperationBudgets,
1777) -> Result<(), XmlEncError> {
1778    context.policy.resources.validate()?;
1779    document.validate_operation_policy(&context.policy.xml, &context.policy.resources)?;
1780    let (target, target_len, target_start, replacement_type) = document.with_view(|view| {
1781        let start = match selector {
1782            DocumentEncryptedDataSelector::UniqueBelowStartNode(Some(id))
1783            | DocumentEncryptedDataSelector::FirstBelowStartNode(Some(id)) => view
1784                .node_for_id(id, context.id_attributes)
1785                .ok_or_else(|| XmlEncError::SelectedNodeUnavailable { id: id.to_owned() })
1786                .and_then(|identity| view.resolve_node(identity).map_err(XmlEncError::from))?,
1787            DocumentEncryptedDataSelector::UniqueBelowStartNode(None)
1788            | DocumentEncryptedDataSelector::FirstBelowStartNode(None)
1789            | DocumentEncryptedDataSelector::EncryptedDataId(_) => view.document().root(),
1790        };
1791        let encrypted_data_id = match selector {
1792            DocumentEncryptedDataSelector::EncryptedDataId(id) => id,
1793            DocumentEncryptedDataSelector::UniqueBelowStartNode(_)
1794            | DocumentEncryptedDataSelector::FirstBelowStartNode(_) => None,
1795        };
1796        let mut matches = start.descendants().filter(|node| {
1797            node.has_tag_name((XMLENC_NS, "EncryptedData"))
1798                && encrypted_data_id.is_none_or(|id| node.attribute("Id") == Some(id))
1799        });
1800        let selected = matches.next().ok_or(XmlEncError::EncryptedDataNotFound)?;
1801        if matches!(
1802            selector,
1803            DocumentEncryptedDataSelector::EncryptedDataId(_)
1804                | DocumentEncryptedDataSelector::UniqueBelowStartNode(_)
1805        ) && matches.next().is_some()
1806        {
1807            return Err(XmlEncError::AmbiguousEncryptedData);
1808        }
1809        Ok::<_, XmlEncError>((
1810            view.node_identity(selected),
1811            selected.range().len(),
1812            selected.range().start,
1813            match selected.attribute("Type") {
1814                Some("http://www.w3.org/2001/04/xmlenc#Element") => {
1815                    Some(EncryptedDataType::Element)
1816                }
1817                Some("http://www.w3.org/2001/04/xmlenc#Content") => {
1818                    Some(EncryptedDataType::Content)
1819                }
1820                _ => None,
1821            },
1822        ))
1823    })?;
1824    let document_binding = Some((document.identity(), document.generation()));
1825    let mut processed = context.process_decryption_input(
1826        DecryptionInput::Xml {
1827            source: document.as_xml(),
1828            node_start: Some(target_start),
1829        },
1830        document_binding,
1831        true,
1832        budgets,
1833        |candidate, operation_parse_budget| {
1834            let DecryptedContent::Xml(plaintext) = candidate else {
1835                return Err(XmlEncError::ReplacementRequiresXml);
1836            };
1837            validate_encryption_document_len(
1838                document
1839                    .as_xml()
1840                    .len()
1841                    .saturating_sub(target_len)
1842                    .saturating_add(plaintext.len()),
1843                &context.policy,
1844            )?;
1845            let settings = context.document_parse_settings();
1846            match replacement_type.as_ref() {
1847                Some(EncryptedDataType::Element) => document
1848                    .prepare_element_replacement_with_budget(
1849                        target,
1850                        &plaintext,
1851                        settings,
1852                        operation_parse_budget,
1853                    )
1854                    .map_err(|error| map_document_error(error, settings)),
1855                Some(EncryptedDataType::Content) => document
1856                    .prepare_node_fragment_replacement_with_budget(
1857                        target,
1858                        &plaintext,
1859                        settings,
1860                        operation_parse_budget,
1861                    )
1862                    .map_err(|error| map_document_error(error, settings)),
1863                Some(EncryptedDataType::Other(_)) | None => {
1864                    Err(XmlEncError::ReplacementRequiresXml)
1865                }
1866            }
1867        },
1868    )?;
1869    let mutation = processed.mutation.ok_or_else(|| {
1870        XmlEncError::OperationPlan("decryption mutation node is unavailable".into())
1871    })?;
1872    processed
1873        .operation
1874        .run_document_transition(mutation, document, |document, _| {
1875            document
1876                .commit_prepared(processed.output)
1877                .map_err(|error| map_document_error(error, context.document_parse_settings()))
1878        })
1879}
1880
1881fn validate_encryption_document_len(
1882    actual: usize,
1883    policy: &crate::policy::DecryptionPolicy,
1884) -> Result<(), XmlEncError> {
1885    policy.resources.validate_xml_document_len(actual)?;
1886    Ok(())
1887}
1888
1889fn validate_recipient_count(actual: usize, maximum: usize) -> Result<(), XmlEncError> {
1890    if actual > maximum {
1891        return Err(crate::policy::PolicyViolation::ResourceLimit {
1892            resource: crate::policy::resource_name::ENCRYPTION_RECIPIENTS,
1893            maximum,
1894            actual,
1895        }
1896        .into());
1897    }
1898    Ok(())
1899}
1900
1901/// Decrypt an already parsed `EncryptedData` value.
1902pub fn decrypt_data(
1903    encrypted: &EncryptedData,
1904    resolver: &dyn DecryptionKeyResolver,
1905) -> Result<DecryptedContent, XmlEncError> {
1906    DecryptContext::new(resolver).decrypt_data(encrypted)
1907}
1908
1909fn resolve_content_key_candidates(
1910    provider: &dyn crate::provider::CryptoProvider,
1911    algorithm: DataEncryptionAlgorithm,
1912    encrypted: &EncryptedData,
1913    resolver: &dyn DecryptionKeyResolver,
1914    policy: &crate::policy::DecryptionPolicy,
1915    budget: &mut KeyCandidateBudget,
1916    mut document: Option<KeySourceDocument<'_, '_>>,
1917) -> Result<Vec<crate::provider::RecoveredContentKey>, XmlEncError> {
1918    let mut last_error = None;
1919    let mut candidates = if encrypted.derived_keys.is_empty()
1920        && encrypted.agreement_methods.is_empty()
1921        && encrypted.encapsulation_methods.is_empty()
1922    {
1923        match resolve_candidates_with_budget(resolver, provider, algorithm, None, policy, budget) {
1924            Ok(keys) => keys,
1925            Err(error) => {
1926                record_candidate_source_error_or_fail_operation(error, &mut last_error)?;
1927                Vec::new()
1928            }
1929        }
1930    } else {
1931        Vec::new()
1932    };
1933    for descriptor in &encrypted.encapsulation_methods {
1934        policy
1935            .key_establishment
1936            .check_encapsulation(descriptor.algorithm)?;
1937        let remaining_before = budget.remaining();
1938        budget.require_available(1)?;
1939        match resolver.resolve_encapsulation_content_keys_with_policy(
1940            provider, algorithm, descriptor, policy, budget,
1941        ) {
1942            Ok(keys) => {
1943                budget.account_returned_candidates(remaining_before, keys.len())?;
1944                candidates.extend(keys);
1945            }
1946            Err(error) => record_candidate_source_error_or_fail_operation(error, &mut last_error)?,
1947        }
1948    }
1949    for descriptor in &encrypted.agreement_methods {
1950        let remaining_before = budget.remaining();
1951        match resolver.resolve_agreement_content_keys_with_policy(
1952            provider, algorithm, descriptor, policy, budget,
1953        ) {
1954            Ok(keys) => {
1955                budget.account_returned_candidates(remaining_before, keys.len())?;
1956                candidates.extend(keys);
1957            }
1958            Err(error) => record_candidate_source_error_or_fail_operation(error, &mut last_error)?,
1959        }
1960    }
1961    for descriptor in &encrypted.derived_keys {
1962        if !reference_list_applies_to_target(
1963            descriptor.reference_list.as_ref(),
1964            ReferenceTarget::new(
1965                document,
1966                encrypted.id.as_deref(),
1967                policy.transforms.same_document_id_semantics,
1968            ),
1969            ReferenceKind::Data,
1970        ) {
1971            continue;
1972        }
1973        let remaining_before = budget.remaining();
1974        match resolver.resolve_derived_content_keys_with_policy(
1975            provider, algorithm, descriptor, policy, budget,
1976        ) {
1977            Ok(keys) => {
1978                budget.account_returned_candidates(remaining_before, keys.len())?;
1979                candidates.extend(keys);
1980            }
1981            Err(error) => record_candidate_source_error_or_fail_operation(error, &mut last_error)?,
1982        }
1983    }
1984    for encrypted_key in &encrypted.encrypted_keys {
1985        let source = KeySourceView {
1986            key: encrypted_key,
1987            document: take_key_document(&mut document, encrypted_key)?,
1988        };
1989        if !encrypted_key_applies_to_data(
1990            encrypted_key,
1991            encrypted,
1992            ReferenceTarget::new(
1993                document,
1994                encrypted.id.as_deref(),
1995                policy.transforms.same_document_id_semantics,
1996            ),
1997        ) {
1998            continue;
1999        }
2000        if let Err(error) = validate_encrypted_key_policy(encrypted_key, policy) {
2001            last_error = Some(error);
2002            continue;
2003        }
2004        let resolution = if encrypted_key.sources.is_empty() {
2005            resolve_candidates_with_budget(
2006                resolver,
2007                provider,
2008                algorithm,
2009                Some(encrypted_key),
2010                policy,
2011                budget,
2012            )
2013        } else {
2014            resolve_nested_key(
2015                provider,
2016                source,
2017                algorithm.key_len(),
2018                resolver,
2019                policy,
2020                budget,
2021                1,
2022            )
2023        };
2024        match resolution {
2025            Ok(keys) => candidates.extend(keys),
2026            Err(error) => record_candidate_source_error_or_fail_operation(error, &mut last_error)?,
2027        }
2028    }
2029    if candidates.is_empty() {
2030        Err(last_error.unwrap_or(XmlEncError::KeyNotFound))
2031    } else {
2032        Ok(candidates)
2033    }
2034}
2035
2036pub(super) fn resolve_nested_cipher_references<'a>(
2037    keys: &mut [EncryptedKey],
2038    document: &crate::XmlDomDocument<'_>,
2039    origins: &mut core::slice::Iter<'_, Option<crate::NodeId>>,
2040    context: &super::cipher_reference::BoundCipherReferenceContext<'a, 'a>,
2041    parse: &XmlParseWorkBudget,
2042) -> Result<(), XmlEncError> {
2043    // The metadata pass already enforces the non-configurable key recursion
2044    // ceiling. Keep original nodes and one shared transform/resource context.
2045    for key in keys {
2046        let origin = origins
2047            .next()
2048            .ok_or_else(|| XmlEncError::OperationPlan("missing encrypted key origin".into()))?;
2049        if let super::CipherData::Reference { uri, transforms } = &key.cipher_data {
2050            let node = origin.and_then(|id| document.get_node(id)).ok_or_else(|| {
2051                XmlEncError::OperationPlan("unresolved foreign encrypted key origin".into())
2052            })?;
2053            key.cipher_data = super::CipherData::Bytes(context.resolve_parsed(
2054                cipher_reference_node(node)?,
2055                uri,
2056                transforms,
2057                parse,
2058            )?);
2059        }
2060        resolve_nested_cipher_references(
2061            &mut key.sources.encrypted_keys,
2062            document,
2063            origins,
2064            context,
2065            parse,
2066        )?;
2067    }
2068    Ok(())
2069}
2070
2071fn resolve_nested_key(
2072    provider: &dyn crate::provider::CryptoProvider,
2073    source: KeySourceView<'_, '_>,
2074    output_len: usize,
2075    resolver: &dyn DecryptionKeyResolver,
2076    policy: &crate::policy::DecryptionPolicy,
2077    budget: &mut KeyCandidateBudget,
2078    depth: usize,
2079) -> Result<Vec<crate::provider::RecoveredContentKey>, XmlEncError> {
2080    let key = source.key;
2081    let mut document = source.document;
2082    let target = ReferenceTarget::new(
2083        document,
2084        key.id.as_deref(),
2085        policy.transforms.same_document_id_semantics,
2086    );
2087    policy.resources.validate_key_info_reference_depth(depth)?;
2088    validate_encrypted_key_policy(key, policy)?;
2089    let wrap = KeyWrapAlgorithm::from_uri(&key.encryption_method.algorithm)?;
2090    provider.require_capability(crate::provider::ProviderCapability::KeyUnwrap(wrap))?;
2091    let ciphertext = key.cipher_data.octets()?;
2092    let expected = wrap
2093        .wrapped_len(output_len)
2094        .ok_or_else(|| XmlEncError::UnsupportedAlgorithm(wrap.uri().into()))?;
2095    if ciphertext.len() != expected {
2096        return Err(XmlEncError::InvalidWrappedKeyLength {
2097            expected,
2098            actual: ciphertext.len(),
2099        });
2100    }
2101    let mut keys = Vec::new();
2102    let mut last_error = None;
2103    let mut resolve_source = |source| -> Result<(), XmlEncError> {
2104        budget.require_available(1)?;
2105        let remaining = budget.remaining();
2106        match resolver
2107            .resolve_key_encryption_keys_with_policy(provider, wrap, source, policy, budget)
2108        {
2109            Ok(resolved) => {
2110                budget.account_returned_candidates(remaining, resolved.len())?;
2111                keys.extend(resolved);
2112                Ok(())
2113            }
2114            Err(error) => record_candidate_source_error_or_fail_operation(error, &mut last_error),
2115        }
2116    };
2117    for mechanism in &key.sources.encapsulation_methods {
2118        resolve_source(KeyEncryptionKeySource::Encapsulation(mechanism))?;
2119    }
2120    for agreement in &key.sources.agreement_methods {
2121        resolve_source(KeyEncryptionKeySource::Agreement(agreement))?;
2122    }
2123    for derived in &key.sources.derived_keys {
2124        if !reference_list_applies_to_target(
2125            derived.reference_list.as_ref(),
2126            target,
2127            ReferenceKind::Key,
2128        ) {
2129            continue;
2130        }
2131        resolve_source(KeyEncryptionKeySource::Derived(derived))?;
2132    }
2133    for nested in &key.sources.encrypted_keys {
2134        let source = KeySourceView {
2135            key: nested,
2136            document: take_key_document(&mut document, nested)?,
2137        };
2138        if !reference_list_applies_to_target(
2139            nested.reference_list.as_ref(),
2140            target,
2141            ReferenceKind::Key,
2142        ) {
2143            continue;
2144        }
2145        let resolved = if nested.sources.is_empty() {
2146            budget.require_available(3)?;
2147            let remaining = budget.remaining();
2148            resolver
2149                .resolve_key_encryption_keys_with_policy(
2150                    provider,
2151                    wrap,
2152                    KeyEncryptionKeySource::Encrypted(nested),
2153                    policy,
2154                    budget,
2155                )
2156                .and_then(|resolved| {
2157                    budget.account_returned_candidates(remaining, resolved.len())?;
2158                    Ok(resolved)
2159                })
2160        } else {
2161            resolve_nested_key(
2162                provider,
2163                source,
2164                wrap.key_len(),
2165                resolver,
2166                policy,
2167                budget,
2168                depth + 1,
2169            )
2170        };
2171        match resolved {
2172            Ok(resolved) => keys.extend(resolved),
2173            Err(error) => record_candidate_source_error_or_fail_operation(error, &mut last_error)?,
2174        }
2175    }
2176    let mut outputs = Vec::new();
2177    // XMLEnc §3.5.1 supplies recovered octets directly to the consuming
2178    // EncryptionMethod. No content-algorithm proxy or base64 round trip.
2179    // https://www.w3.org/TR/2013/REC-xmlenc-core1-20130411/#sec-EncryptedKey
2180    for kek in keys {
2181        if kek.key_len() != wrap.key_len() {
2182            last_error = Some(XmlEncError::InvalidKekSize {
2183                algorithm: wrap,
2184                expected: wrap.key_len(),
2185                actual: kek.key_len(),
2186            });
2187            continue;
2188        }
2189        budget.consume(1)?;
2190        match kek.unwrap_nested(provider, wrap, &ciphertext, output_len) {
2191            Ok(result) if result.key_len() == output_len => outputs.push(result),
2192            Ok(result) => {
2193                last_error = Some(XmlEncError::InvalidWrappedKeyLength {
2194                    expected: output_len,
2195                    actual: result.key_len(),
2196                })
2197            }
2198            Err(error) => last_error = Some(XmlEncError::Provider(error)),
2199        }
2200    }
2201    if outputs.is_empty() {
2202        Err(last_error.unwrap_or(XmlEncError::KeyNotFound))
2203    } else {
2204        Ok(outputs)
2205    }
2206}
2207
2208#[derive(Clone, Copy)]
2209struct KeySourceDocument<'a, 'doc> {
2210    references: &'a super::cipher_reference::BoundCipherReferenceContext<'doc, 'doc>,
2211    target: crate::NodeId,
2212    origins: &'a [Option<crate::NodeId>],
2213}
2214
2215struct KeySourceView<'a, 'doc> {
2216    key: &'a EncryptedKey,
2217    document: Option<KeySourceDocument<'a, 'doc>>,
2218}
2219
2220fn key_origin_count(key: &EncryptedKey) -> usize {
2221    // Metadata validation has already bounded total candidates and recursion.
2222    // Each identity is counted at most once per bounded key-indirection level.
2223    1 + key
2224        .sources
2225        .encrypted_keys
2226        .iter()
2227        .map(key_origin_count)
2228        .sum::<usize>()
2229}
2230
2231fn take_key_document<'a, 'doc>(
2232    document: &mut Option<KeySourceDocument<'a, 'doc>>,
2233    key: &EncryptedKey,
2234) -> Result<Option<KeySourceDocument<'a, 'doc>>, XmlEncError> {
2235    let Some(parent) = document else {
2236        return Ok(None);
2237    };
2238    let count = key_origin_count(key);
2239    if count > parent.origins.len() {
2240        return Err(XmlEncError::OperationPlan(
2241            "missing key association origin".into(),
2242        ));
2243    }
2244    let (subtree, remaining) = parent.origins.split_at(count);
2245    parent.origins = remaining;
2246    Ok(subtree[0].map(|target| KeySourceDocument {
2247        references: parent.references,
2248        target,
2249        origins: &subtree[1..],
2250    }))
2251}
2252
2253#[derive(Clone, Copy)]
2254enum ReferenceTarget<'a, 'doc> {
2255    Document {
2256        references: &'a super::cipher_reference::BoundCipherReferenceContext<'doc, 'doc>,
2257        node: crate::NodeId,
2258    },
2259    TypedId {
2260        id: Option<&'a str>,
2261        semantics: crate::policy::SameDocumentIdSemantics,
2262    },
2263}
2264
2265impl<'a, 'doc> ReferenceTarget<'a, 'doc> {
2266    fn new(
2267        document: Option<KeySourceDocument<'a, 'doc>>,
2268        id: Option<&'a str>,
2269        semantics: crate::policy::SameDocumentIdSemantics,
2270    ) -> Self {
2271        match document {
2272            Some(source) => Self::Document {
2273                references: source.references,
2274                node: source.target,
2275            },
2276            None => Self::TypedId { id, semantics },
2277        }
2278    }
2279
2280    fn matches(self, uri: &str) -> bool {
2281        // XMLEnc 1.1 §3.6 references objects through URIs, not specifically the
2282        // lexical Id attribute. XML operations retain the original document's
2283        // registered IDs, ambiguity checks and configured fragment grammar.
2284        // Typed-only requests have no DOM and can use only their explicit Id.
2285        // https://www.w3.org/TR/2013/REC-xmlenc-core1-20130411/#sec-ReferenceList
2286        match self {
2287            Self::Document { references, node } => references
2288                .resolver()
2289                .same_document_reference_targets(uri, node),
2290            Self::TypedId { id, semantics } => {
2291                id.is_some_and(|id| reference_targets_id(uri, id, semantics))
2292            }
2293        }
2294    }
2295}
2296
2297enum ReferenceKind {
2298    Data,
2299    Key,
2300}
2301
2302fn record_candidate_source_error_or_fail_operation(
2303    error: XmlEncError,
2304    last_error: &mut Option<XmlEncError>,
2305) -> Result<(), XmlEncError> {
2306    // Candidate-specific failures permit the next ordered key source. The
2307    // shared work ceiling is operation-wide and must never be recoverable by
2308    // advancing to another recipient.
2309    if matches!(
2310        &error,
2311        XmlEncError::Policy(_)
2312            | XmlEncError::KeyEstablishment(
2313                crate::key_establishment::KeyEstablishmentError::Policy(_)
2314            )
2315    ) {
2316        return Err(error);
2317    }
2318    *last_error = Some(error);
2319    Ok(())
2320}
2321
2322fn resolve_candidates_with_budget(
2323    resolver: &dyn DecryptionKeyResolver,
2324    provider: &dyn crate::provider::CryptoProvider,
2325    algorithm: DataEncryptionAlgorithm,
2326    encrypted_key: Option<&EncryptedKey>,
2327    policy: &crate::policy::DecryptionPolicy,
2328    budget: &mut KeyCandidateBudget,
2329) -> Result<Vec<crate::provider::RecoveredContentKey>, XmlEncError> {
2330    let remaining_before = budget.remaining();
2331    let keys = resolver.resolve_content_keys_with_policy(
2332        provider,
2333        algorithm,
2334        encrypted_key,
2335        policy,
2336        budget,
2337    )?;
2338    budget.account_returned_candidates(remaining_before, keys.len())?;
2339    Ok(keys)
2340}
2341
2342fn encrypted_key_applies_to_data(
2343    encrypted_key: &EncryptedKey,
2344    encrypted_data: &EncryptedData,
2345    target: ReferenceTarget<'_, '_>,
2346) -> bool {
2347    // XMLEnc association metadata is optional, but authoritative when present:
2348    // DataReference identifies encrypted objects and CarriedKeyName identifies
2349    // the transported key referenced by the enclosing ds:KeyName.
2350    if !reference_list_applies_to_target(
2351        encrypted_key.reference_list.as_ref(),
2352        target,
2353        ReferenceKind::Data,
2354    ) {
2355        return false;
2356    }
2357    if let (Some(carried), Some(expected)) = (
2358        encrypted_key.carried_key_name.as_deref(),
2359        encrypted_data.key_name.as_deref(),
2360    ) && carried != expected
2361    {
2362        return false;
2363    }
2364    true
2365}
2366
2367pub(super) struct EncapsulationRecipient {
2368    key_path: [usize; crate::hard_limits::KEY_INFO_REFERENCE_DEPTH_CEILING],
2369    depth: usize,
2370    method: usize,
2371}
2372
2373impl EncapsulationRecipient {
2374    pub(super) fn mechanism<'a>(
2375        &self,
2376        data: &'a EncryptedData,
2377    ) -> &'a crate::key_establishment::EncapsulationMechanism {
2378        let mut methods = &data.encapsulation_methods;
2379        let mut keys = &data.encrypted_keys;
2380        for index in &self.key_path[..self.depth] {
2381            let sources = &keys[*index].sources;
2382            methods = &sources.encapsulation_methods;
2383            keys = &sources.encrypted_keys;
2384        }
2385        &methods[self.method]
2386    }
2387}
2388
2389pub(super) fn select_encapsulation_recipient<'doc>(
2390    data: &EncryptedData,
2391    references: &super::cipher_reference::BoundCipherReferenceContext<'doc, 'doc>,
2392    target: crate::NodeId,
2393    origins: &[Option<crate::NodeId>],
2394    policy: &crate::policy::DecryptionPolicy,
2395) -> Result<Option<EncapsulationRecipient>, XmlEncError> {
2396    struct Selection<'a> {
2397        data: &'a EncryptedData,
2398        semantics: crate::policy::SameDocumentIdSemantics,
2399        path: [usize; crate::hard_limits::KEY_INFO_REFERENCE_DEPTH_CEILING],
2400        depth: usize,
2401        selected: Option<EncapsulationRecipient>,
2402    }
2403    fn visit(
2404        methods: &[crate::key_establishment::EncapsulationMechanism],
2405        keys: &[EncryptedKey],
2406        mut document: Option<KeySourceDocument<'_, '_>>,
2407        parent: Option<&EncryptedKey>,
2408        selection: &mut Selection<'_>,
2409    ) -> Result<(), XmlEncError> {
2410        for method in 0..methods.len() {
2411            if selection.selected.is_some() {
2412                return Err(XmlEncError::InvalidStructure(
2413                    "multiple KEM recipients require explicit application key resolution".into(),
2414                ));
2415            }
2416            selection.selected = Some(EncapsulationRecipient {
2417                key_path: selection.path,
2418                depth: selection.depth,
2419                method,
2420            });
2421        }
2422        for (index, key) in keys.iter().enumerate() {
2423            let source = take_key_document(&mut document, key)?;
2424            let target = ReferenceTarget::new(
2425                document,
2426                match parent {
2427                    Some(parent) => parent.id.as_deref(),
2428                    None => selection.data.id.as_deref(),
2429                },
2430                selection.semantics,
2431            );
2432            // XMLEnc 1.1 §3.6: use the execution predicates for DataReference/CarriedKeyName
2433            // at the content-key boundary, KeyReference at wrapping-key boundaries.
2434            // https://www.w3.org/TR/2013/REC-xmlenc-core1-20130411/#sec-ReferenceList
2435            let applies = match parent {
2436                None => encrypted_key_applies_to_data(key, selection.data, target),
2437                Some(_) => reference_list_applies_to_target(
2438                    key.reference_list.as_ref(),
2439                    target,
2440                    ReferenceKind::Key,
2441                ),
2442            };
2443            if !applies {
2444                continue;
2445            }
2446            // Parsing bounds every key-indirection path by this absolute
2447            // ceiling; selection needs no heap allocation or ciphertext copy.
2448            debug_assert!(selection.depth < selection.path.len());
2449            selection.path[selection.depth] = index;
2450            selection.depth += 1;
2451            visit(
2452                &key.sources.encapsulation_methods,
2453                &key.sources.encrypted_keys,
2454                source,
2455                Some(key),
2456                selection,
2457            )?;
2458            selection.depth -= 1;
2459        }
2460        Ok(())
2461    }
2462    let mut selection = Selection {
2463        data,
2464        semantics: policy.transforms.same_document_id_semantics,
2465        path: [0; crate::hard_limits::KEY_INFO_REFERENCE_DEPTH_CEILING],
2466        depth: 0,
2467        selected: None,
2468    };
2469    visit(
2470        &data.encapsulation_methods,
2471        &data.encrypted_keys,
2472        Some(KeySourceDocument {
2473            references,
2474            target,
2475            origins,
2476        }),
2477        None,
2478        &mut selection,
2479    )?;
2480    Ok(selection.selected)
2481}
2482
2483fn reference_list_applies_to_target(
2484    references: Option<&super::ReferenceList>,
2485    target: ReferenceTarget<'_, '_>,
2486    kind: ReferenceKind,
2487) -> bool {
2488    let Some(references) = references else {
2489        return true;
2490    };
2491    let uris = match kind {
2492        ReferenceKind::Data => &references.data_references,
2493        ReferenceKind::Key => &references.key_references,
2494    };
2495    if uris.is_empty() {
2496        return true;
2497    }
2498    for uri in uris {
2499        if target.matches(uri) {
2500            return true;
2501        }
2502    }
2503    false
2504}
2505
2506fn reference_targets_id(
2507    uri: &str,
2508    id: &str,
2509    semantics: crate::policy::SameDocumentIdSemantics,
2510) -> bool {
2511    let Some(fragment) = uri.strip_prefix('#') else {
2512        return false;
2513    };
2514    // Share XMLDSig's XPointer grammar rather than treating every fragment as
2515    // an ID string; XMLEnc §3.6 uses URI references for both object classes.
2516    // https://www.w3.org/TR/2013/REC-xmlenc-core1-20130411/#sec-ReferenceList
2517    let Ok(fragment) =
2518        crate::xmldsig::uri::UriReferenceResolver::normalize_fragment(fragment, None, semantics)
2519    else {
2520        return false;
2521    };
2522    crate::xmldsig::uri::UriReferenceResolver::id_fragment(&fragment, semantics)
2523        .is_some_and(|(target, _)| target == id)
2524}
2525
2526fn compatible_decryption_key_candidates(
2527    algorithm: DataEncryptionAlgorithm,
2528    mut keys: Vec<crate::provider::RecoveredContentKey>,
2529) -> Result<Vec<crate::provider::RecoveredContentKey>, XmlEncError> {
2530    let mut accepted = 0;
2531    let mut last_error = None;
2532    for index in 0..keys.len() {
2533        let key = &keys[index];
2534        match validate_content_key_len(algorithm, key.key_len()) {
2535            Ok(())
2536                if !keys[..accepted]
2537                    .iter()
2538                    .any(|existing| existing.same_candidate(key)) =>
2539            {
2540                // Accepted entries form a stable prefix. Swapping an earlier
2541                // rejected slot never moves an unvisited entry, and avoids a
2542                // second allocation without changing candidate precedence.
2543                keys.swap(accepted, index);
2544                accepted += 1;
2545            }
2546            Ok(()) => {}
2547            Err(error) => last_error = Some(error),
2548        }
2549    }
2550    keys.truncate(accepted);
2551    if keys.is_empty() {
2552        return Err(last_error.unwrap_or(XmlEncError::KeyNotFound));
2553    }
2554    Ok(keys)
2555}
2556
2557fn validate_decryption_key_candidates(
2558    algorithm: DataEncryptionAlgorithm,
2559    actual: usize,
2560) -> Result<(), XmlEncError> {
2561    let maximum = crate::hard_limits::KEY_CANDIDATE_CEILING;
2562    if actual > maximum {
2563        return Err(crate::policy::PolicyViolation::ResourceLimit {
2564            resource: crate::policy::resource_name::KEY_CANDIDATES,
2565            maximum,
2566            actual,
2567        }
2568        .into());
2569    }
2570    if actual > 1 && !algorithm.is_authenticated() {
2571        return Err(XmlEncError::AmbiguousKeyCandidates { algorithm, actual });
2572    }
2573    Ok(())
2574}
2575
2576fn validate_encrypted_key_policy(
2577    encrypted_key: &EncryptedKey,
2578    policy: &crate::policy::DecryptionPolicy,
2579) -> Result<(), XmlEncError> {
2580    validate_key_encryption_method_policy(&encrypted_key.encryption_method, policy)
2581}
2582
2583pub(super) fn validate_key_encryption_method_policy(
2584    method: &super::EncryptionMethod,
2585    policy: &crate::policy::DecryptionPolicy,
2586) -> Result<(), XmlEncError> {
2587    method.validate_structure()?;
2588    let uri = &method.algorithm;
2589    if let Ok(transport) = KeyTransportAlgorithm::from_uri(uri) {
2590        if policy
2591            .key_transport_algorithms
2592            .as_ref()
2593            .map_or(transport.requires_explicit_permission(), |allowed| {
2594                !allowed.contains(&transport)
2595            })
2596        {
2597            return Err(crate::policy::PolicyViolation::Algorithm {
2598                operation: "decryption",
2599                algorithm: uri.clone(),
2600            }
2601            .into());
2602        }
2603        #[cfg(feature = "legacy-algorithms")]
2604        if transport == KeyTransportAlgorithm::RsaPkcs1v15 {
2605            return Ok(());
2606        }
2607        let digest = parse_oaep_digest(method.oaep_digest.as_deref())?;
2608        let mgf_digest = parse_oaep_mgf_digest(method.mgf_algorithm.as_deref())?;
2609        let selected_algorithms = [
2610            (
2611                digest,
2612                method.oaep_digest.as_deref().unwrap_or(digest.uri()),
2613            ),
2614            (
2615                mgf_digest,
2616                method
2617                    .mgf_algorithm
2618                    .as_deref()
2619                    .unwrap_or(mgf_digest.mgf_uri().ok_or_else(|| {
2620                        XmlEncError::InvalidStructure(
2621                            "OAEP mask digest has no XML Encryption identifier".into(),
2622                        )
2623                    })?),
2624            ),
2625        ];
2626        for (selected, wire_uri) in selected_algorithms {
2627            if selected.requires_explicit_permission()
2628                && !policy
2629                    .oaep_digests
2630                    .as_ref()
2631                    .is_some_and(|allowed| allowed.contains(&selected))
2632            {
2633                return Err(crate::policy::PolicyViolation::Algorithm {
2634                    operation: "decryption",
2635                    algorithm: wire_uri.to_owned(),
2636                }
2637                .into());
2638            }
2639            if policy
2640                .oaep_digests
2641                .as_ref()
2642                .is_some_and(|allowed| !allowed.contains(&selected))
2643            {
2644                return Err(crate::policy::PolicyViolation::Algorithm {
2645                    operation: "decryption",
2646                    algorithm: wire_uri.to_owned(),
2647                }
2648                .into());
2649            }
2650        }
2651    } else {
2652        let wrap = KeyWrapAlgorithm::from_uri(uri)?;
2653        if policy
2654            .key_wrap_algorithms
2655            .as_ref()
2656            .map_or(wrap.requires_explicit_permission(), |allowed| {
2657                !allowed.contains(&wrap)
2658            })
2659        {
2660            return Err(crate::policy::PolicyViolation::Algorithm {
2661                operation: "decryption",
2662                algorithm: uri.clone(),
2663            }
2664            .into());
2665        }
2666    }
2667    Ok(())
2668}
2669
2670fn validate_content_framing_before_resolution(
2671    algorithm: DataEncryptionAlgorithm,
2672    ciphertext_len: usize,
2673    encrypted_keys: &[EncryptedKey],
2674    policy: &crate::policy::DecryptionPolicy,
2675) -> Result<(), XmlEncError> {
2676    let Err(framing_error) = validate_ciphertext_framing(algorithm, ciphertext_len) else {
2677        return Ok(());
2678    };
2679
2680    // If no embedded key uses a supported transport, that envelope error is
2681    // more specific than content framing: the ciphertext cannot be interpreted
2682    // under any supported key path. This inspection performs no key resolution
2683    // and never dispatches malformed content to a cryptographic provider.
2684    if !encrypted_keys.is_empty() {
2685        let mut last_key_error = None;
2686        for encrypted_key in encrypted_keys {
2687            match validate_encrypted_key_policy(encrypted_key, policy) {
2688                Ok(()) => return Err(framing_error),
2689                Err(error) => last_key_error = Some(error),
2690            }
2691        }
2692        if let Some(error) = last_key_error {
2693            return Err(error);
2694        }
2695    }
2696    Err(framing_error)
2697}
2698
2699fn validate_typed_cipher_values(
2700    encrypted: &EncryptedData,
2701    algorithm: DataEncryptionAlgorithm,
2702    maximum_plaintext: usize,
2703    maximum_cipher_values: usize,
2704) -> Result<(), XmlEncError> {
2705    let maximum_ciphertext = algorithm
2706        .ciphertext_len_for_plaintext(maximum_plaintext)
2707        .ok_or(XmlEncError::InvalidEncryptionConfig(
2708            "ciphertext size overflow".into(),
2709        ))?;
2710    let projected = validate_cipher_data_len(&encrypted.cipher_data, maximum_ciphertext)?;
2711    if projected > maximum_ciphertext {
2712        return Err(crate::policy::PolicyViolation::ResourceLimit {
2713            resource: crate::policy::resource_name::ENCRYPTION_PLAINTEXT_BYTES,
2714            maximum: maximum_plaintext,
2715            actual: projected.saturating_sub(algorithm.minimum_ciphertext_len()),
2716        }
2717        .into());
2718    }
2719
2720    let mut aggregate_encoded = cipher_data_storage_len(&encrypted.cipher_data);
2721    if aggregate_encoded > maximum_cipher_values {
2722        return Err(crate::policy::PolicyViolation::ResourceLimit {
2723            resource: crate::policy::resource_name::AGGREGATE_ENCRYPTION_CIPHER_VALUE_BYTES,
2724            maximum: maximum_cipher_values,
2725            actual: aggregate_encoded,
2726        }
2727        .into());
2728    }
2729
2730    let maximum_wrapped_key = projected_decoded_len_for_encoded_len(MAX_CIPHER_VALUE_BASE64_LEN);
2731    validate_encapsulation_cipher_values(
2732        &encrypted.encapsulation_methods,
2733        maximum_cipher_values,
2734        &mut aggregate_encoded,
2735    )?;
2736    validate_wrapped_cipher_values(
2737        &encrypted.encrypted_keys,
2738        maximum_wrapped_key,
2739        maximum_cipher_values,
2740        &mut aggregate_encoded,
2741    )
2742}
2743
2744fn validate_wrapped_cipher_values(
2745    keys: &[EncryptedKey],
2746    maximum_wrapped_key: usize,
2747    maximum_cipher_values: usize,
2748    aggregate_encoded: &mut usize,
2749) -> Result<(), XmlEncError> {
2750    for encrypted_key in keys {
2751        validate_cipher_data_len(&encrypted_key.cipher_data, maximum_wrapped_key)?;
2752        *aggregate_encoded =
2753            aggregate_encoded.saturating_add(cipher_data_storage_len(&encrypted_key.cipher_data));
2754        if *aggregate_encoded > maximum_cipher_values {
2755            return Err(crate::policy::PolicyViolation::ResourceLimit {
2756                resource: crate::policy::resource_name::AGGREGATE_ENCRYPTION_CIPHER_VALUE_BYTES,
2757                maximum: maximum_cipher_values,
2758                actual: *aggregate_encoded,
2759            }
2760            .into());
2761        }
2762        validate_encapsulation_cipher_values(
2763            &encrypted_key.sources.encapsulation_methods,
2764            maximum_cipher_values,
2765            aggregate_encoded,
2766        )?;
2767        validate_wrapped_cipher_values(
2768            &encrypted_key.sources.encrypted_keys,
2769            maximum_wrapped_key,
2770            maximum_cipher_values,
2771            aggregate_encoded,
2772        )?;
2773    }
2774    Ok(())
2775}
2776
2777fn validate_encapsulation_cipher_values(
2778    methods: &[crate::key_establishment::EncapsulationMechanism],
2779    maximum: usize,
2780    aggregate: &mut usize,
2781) -> Result<(), XmlEncError> {
2782    for method in methods {
2783        // Match XML parsing's encoded CipherValue accounting, even though
2784        // typed descriptors retain decoded public ciphertext bytes.
2785        let encoded = method.ciphertext.len().div_ceil(3).checked_mul(4);
2786        let total = encoded.and_then(|encoded| aggregate.checked_add(encoded));
2787        let total = total.ok_or(crate::policy::PolicyViolation::ResourceLimitExceeded {
2788            resource: crate::policy::resource_name::AGGREGATE_ENCRYPTION_CIPHER_VALUE_BYTES,
2789            maximum,
2790        })?;
2791        if total > maximum {
2792            return Err(crate::policy::PolicyViolation::ResourceLimit {
2793                resource: crate::policy::resource_name::AGGREGATE_ENCRYPTION_CIPHER_VALUE_BYTES,
2794                maximum,
2795                actual: total,
2796            }
2797            .into());
2798        }
2799        *aggregate = total;
2800    }
2801    Ok(())
2802}
2803
2804fn cipher_data_storage_len(cipher: &super::CipherData) -> usize {
2805    match cipher {
2806        super::CipherData::Value { value } => value.len(),
2807        super::CipherData::Bytes(bytes) => bytes.len(),
2808        super::CipherData::Reference { .. } => 0,
2809    }
2810}
2811
2812fn validate_cipher_data_len(
2813    cipher: &super::CipherData,
2814    maximum: usize,
2815) -> Result<usize, XmlEncError> {
2816    match cipher {
2817        super::CipherData::Value { value } => validate_cipher_value_len(value, maximum),
2818        super::CipherData::Bytes(bytes) if bytes.len() <= maximum => Ok(bytes.len()),
2819        super::CipherData::Bytes(bytes) => Err(crate::policy::PolicyViolation::ResourceLimit {
2820            resource: crate::policy::resource_name::AGGREGATE_ENCRYPTION_CIPHER_VALUE_BYTES,
2821            maximum,
2822            actual: bytes.len(),
2823        }
2824        .into()),
2825        super::CipherData::Reference { .. } => Ok(0),
2826    }
2827}
2828
2829fn validate_cipher_value_len(value: &str, maximum_decoded: usize) -> Result<usize, XmlEncError> {
2830    if value.len() > MAX_CIPHER_VALUE_BASE64_LEN {
2831        return Err(XmlEncError::InvalidStructure(format!(
2832            "CipherValue exceeds {MAX_CIPHER_VALUE_BASE64_LEN}-byte limit"
2833        )));
2834    }
2835    Ok(projected_decoded_len(value).min(maximum_decoded.saturating_add(1)))
2836}
2837
2838fn projected_decoded_len(value: &str) -> usize {
2839    let padding = value
2840        .as_bytes()
2841        .iter()
2842        .rev()
2843        .take(2)
2844        .take_while(|byte| **byte == b'=')
2845        .count();
2846    projected_decoded_len_for_encoded_len(value.len()).saturating_sub(padding)
2847}
2848
2849fn projected_decoded_len_for_encoded_len(encoded_len: usize) -> usize {
2850    encoded_len
2851        .checked_add(3)
2852        .map(|length| length / 4)
2853        .and_then(|quanta| quanta.checked_mul(3))
2854        .unwrap_or(usize::MAX)
2855}
2856
2857pub(crate) fn validate_key_len(
2858    algorithm: DataEncryptionAlgorithm,
2859    key: &[u8],
2860) -> Result<(), XmlEncError> {
2861    validate_content_key_len(algorithm, key.len())
2862}
2863
2864fn validate_content_key_len(
2865    algorithm: DataEncryptionAlgorithm,
2866    actual: usize,
2867) -> Result<(), XmlEncError> {
2868    if actual == algorithm.key_len() {
2869        Ok(())
2870    } else {
2871        Err(XmlEncError::InvalidKeySize {
2872            algorithm,
2873            expected: algorithm.key_len(),
2874            actual,
2875        })
2876    }
2877}
2878
2879fn validate_possible_plaintext_len(
2880    algorithm: DataEncryptionAlgorithm,
2881    ciphertext_len: usize,
2882    maximum: usize,
2883) -> Result<(), XmlEncError> {
2884    // CBC's minimum includes an IV and one padded block; using that
2885    // maximum-padding case yields the safe pre-decryption plaintext lower bound.
2886    let framing = algorithm.minimum_ciphertext_len();
2887    validate_plaintext_len(ciphertext_len.saturating_sub(framing), maximum)
2888}
2889
2890fn validate_provider_plaintext_len(
2891    algorithm: DataEncryptionAlgorithm,
2892    ciphertext_len: usize,
2893    plaintext_len: usize,
2894) -> Result<(), XmlEncError> {
2895    use crate::provider::{ProviderError, ProviderOperation};
2896
2897    match algorithm.cbc_block_len() {
2898        None => {
2899            let expected = ciphertext_len - algorithm.minimum_ciphertext_len();
2900            if plaintext_len != expected {
2901                return Err(ProviderError::InvalidOutputSize {
2902                    operation: ProviderOperation::Decrypt,
2903                    expected,
2904                    actual: plaintext_len,
2905                }
2906                .into());
2907            }
2908        }
2909        Some(block) => {
2910            let padded_len = ciphertext_len - block;
2911            let minimum = padded_len - block;
2912            let maximum = padded_len - 1;
2913            if !(minimum..=maximum).contains(&plaintext_len) {
2914                return Err(ProviderError::InvalidOutputSizeRange {
2915                    operation: ProviderOperation::Decrypt,
2916                    minimum,
2917                    maximum,
2918                    actual: plaintext_len,
2919                }
2920                .into());
2921            }
2922        }
2923    }
2924    Ok(())
2925}
2926
2927fn validate_plaintext_len(actual: usize, maximum: usize) -> Result<(), XmlEncError> {
2928    if actual <= maximum {
2929        Ok(())
2930    } else {
2931        Err(crate::policy::PolicyViolation::ResourceLimit {
2932            resource: crate::policy::resource_name::ENCRYPTION_PLAINTEXT_BYTES,
2933            maximum,
2934            actual,
2935        }
2936        .into())
2937    }
2938}
2939
2940fn map_data_decryption_error(
2941    algorithm: DataEncryptionAlgorithm,
2942    ciphertext_len: usize,
2943    error: crate::provider::ProviderError,
2944) -> XmlEncError {
2945    use crate::provider::ProviderError;
2946
2947    match error {
2948        ProviderError::AuthenticationFailed if algorithm.is_authenticated() => {
2949            XmlEncError::AeadAuthenticationFailed
2950        }
2951        ProviderError::InvalidInput(crate::provider::ProviderInputError::AesGcmFraming)
2952            if algorithm.cbc_block_len().is_none() =>
2953        {
2954            XmlEncError::DataTooShort {
2955                algorithm: "AES-GCM",
2956                minimum: 28,
2957                actual: ciphertext_len,
2958            }
2959        }
2960        error @ ProviderError::InvalidInput(crate::provider::ProviderInputError::AesCbcFraming)
2961            if algorithm.cbc_block_len().is_some() =>
2962        {
2963            match super::types::validate_ciphertext_framing(algorithm, ciphertext_len) {
2964                Err(error) => error,
2965                Ok(()) => XmlEncError::Provider(error),
2966            }
2967        }
2968        ProviderError::InvalidInput(crate::provider::ProviderInputError::AesCbcCiphertext)
2969            if algorithm.cbc_block_len().is_some() =>
2970        {
2971            XmlEncError::InvalidPadding
2972        }
2973        error => XmlEncError::Provider(error),
2974    }
2975}
2976
2977#[cfg(test)]
2978mod tests {
2979    #[test]
2980    fn raw_chacha_cannot_select_keys_or_confirm_kem_decapsulation() {
2981        // Successful stream decryption is not evidence that a key is correct.
2982        let algorithm = super::DataEncryptionAlgorithm::ChaCha20;
2983        assert!(matches!(
2984            super::validate_decryption_key_candidates(algorithm, 2),
2985            Err(super::XmlEncError::AmbiguousKeyCandidates { actual: 2, .. })
2986        ));
2987        assert!(
2988            crate::policy::KeyEstablishmentPolicy::default()
2989                .check_kem_content(algorithm)
2990                .is_err()
2991        );
2992        let externally_authenticated = crate::policy::KeyEstablishmentPolicy {
2993            kem_content_authentication:
2994                crate::policy::KemContentAuthentication::ExternalAuthenticated,
2995            ..Default::default()
2996        };
2997        assert!(
2998            externally_authenticated
2999                .check_kem_content(algorithm)
3000                .is_ok()
3001        );
3002        for authenticated in [
3003            super::DataEncryptionAlgorithm::Aes128Gcm,
3004            super::DataEncryptionAlgorithm::ChaCha20Poly1305,
3005        ] {
3006            assert!(super::validate_decryption_key_candidates(authenticated, 2).is_ok());
3007            assert!(
3008                crate::policy::KeyEstablishmentPolicy::default()
3009                    .check_kem_content(authenticated)
3010                    .is_ok()
3011            );
3012        }
3013    }
3014
3015    #[test]
3016    fn raw_chacha_public_decryption_rejects_distinct_candidate_keys() {
3017        // A wrong key still produces bytes: the public boundary must reject
3018        // ambiguity rather than return the first candidate's arbitrary plaintext.
3019        let algorithm = DataEncryptionAlgorithm::ChaCha20;
3020        let encrypted = super::super::EncryptedDataBuilder::new(algorithm)
3021            .policy(crate::policy::EncryptionPolicy {
3022                data_algorithms: Some([algorithm].into()),
3023                ..Default::default()
3024            })
3025            .chacha_parameters(super::super::ChaChaParameters {
3026                nonce: Some([1; 12]),
3027                counter: Some(1_u32.to_le_bytes()),
3028                aad: None,
3029            })
3030            .direct_key([7; 32])
3031            .encrypt_binary(b"payload")
3032            .expect("explicitly permitted raw ChaCha encryption");
3033        let resolver = CandidateResolver {
3034            keys: vec![vec![1; 32], vec![7; 32]],
3035        };
3036        assert!(matches!(
3037            DecryptContext::new(&resolver)
3038                .policy(crate::policy::DecryptionPolicy {
3039                    data_algorithms: Some([algorithm].into()),
3040                    ..Default::default()
3041                })
3042                .decrypt(&encrypted.encrypted_data_xml),
3043            Err(XmlEncError::AmbiguousKeyCandidates { actual: 2, .. })
3044        ));
3045    }
3046
3047    #[test]
3048    fn typed_reference_association_obeys_id_semantics() {
3049        // A typed decryption input must not bypass the URI policy enforced by
3050        // the retained-document path, including escaping and Visa3D opt-in.
3051        use crate::policy::SameDocumentIdSemantics as Semantics;
3052        for semantics in [
3053            Semantics::Specification,
3054            Semantics::XmlSecBarename,
3055            Semantics::XmlSecVisa3d,
3056        ] {
3057            assert!(!super::reference_targets_id("#", "", semantics));
3058        }
3059        assert!(super::reference_targets_id(
3060            "#tar%67et",
3061            "target",
3062            Semantics::Specification
3063        ));
3064        assert!(super::reference_targets_id(
3065            "#xpointer(id('target'))",
3066            "target",
3067            Semantics::Specification
3068        ));
3069        assert!(!super::reference_targets_id(
3070            "#123",
3071            "123",
3072            Semantics::Specification
3073        ));
3074        assert!(super::reference_targets_id(
3075            "#123",
3076            "123",
3077            Semantics::XmlSecVisa3d
3078        ));
3079        assert!(!super::reference_targets_id(
3080            "#xpointer(id('a' or 'b'))",
3081            "a' or 'b",
3082            Semantics::Specification
3083        ));
3084    }
3085    #[cfg(feature = "legacy-algorithms")]
3086    #[test]
3087    fn legacy_cbc_rejects_ambiguous_candidates_before_decryption() {
3088        // CBC has no authentication tag: selecting a candidate by padding
3089        // success is unsafe for DES and AES-192 just as for existing AES modes.
3090        for algorithm in [
3091            super::DataEncryptionAlgorithm::TripleDesCbc,
3092            super::DataEncryptionAlgorithm::Aes192Cbc,
3093        ] {
3094            assert!(matches!(
3095                super::validate_decryption_key_candidates(algorithm, 2),
3096                Err(super::XmlEncError::AmbiguousKeyCandidates { actual: 2, .. })
3097            ));
3098        }
3099    }
3100    use std::cell::{Cell, RefCell};
3101    use std::sync::atomic::{AtomicUsize, Ordering};
3102
3103    use crate::rsa_encoding::RsaPrivateKeyEncoding as _;
3104    use aes_gcm::{
3105        Aes128Gcm,
3106        aead::{AeadInOut, KeyInit},
3107    };
3108    use aes_kw::KwAes128;
3109    use base64::engine::general_purpose::STANDARD;
3110    use rand_chacha::{ChaCha20Rng, rand_core::SeedableRng};
3111    use rsa::{Oaep, RsaPublicKey};
3112    use sha1::Sha1;
3113    use sha2::{Sha256, Sha384};
3114
3115    use super::*;
3116    use crate::xmlenc::{CipherData, EncryptionMethod};
3117
3118    struct RecipientKeyResolver {
3119        recipient: &'static str,
3120        key: Vec<u8>,
3121    }
3122
3123    struct CountingResolver {
3124        candidate_calls: Cell<usize>,
3125        key: Vec<u8>,
3126    }
3127
3128    struct AllCallsResolver {
3129        calls: Cell<usize>,
3130        key: Vec<u8>,
3131    }
3132
3133    struct CandidateResolver {
3134        keys: Vec<Vec<u8>>,
3135    }
3136
3137    #[test]
3138    fn policy_unaware_recipient_resolver_is_not_dispatched() {
3139        // An already-recovered AES key cannot prove that its RSA source met
3140        // the operation's minimum. Reject the legacy recipient path before
3141        // lookup, while keeping direct symmetric resolution available.
3142        struct LegacyRecipient {
3143            recipient_calls: Cell<usize>,
3144            key: Vec<u8>,
3145        }
3146        impl DecryptionKeyResolver for LegacyRecipient {
3147            fn resolve_key(
3148                &self,
3149                _provider: &dyn crate::provider::CryptoProvider,
3150                _algorithm: DataEncryptionAlgorithm,
3151                encrypted_key: Option<&EncryptedKey>,
3152            ) -> Result<Vec<u8>, XmlEncError> {
3153                if encrypted_key.is_none() {
3154                    return Err(XmlEncError::KeyNotFound);
3155                }
3156                self.recipient_calls.set(self.recipient_calls.get() + 1);
3157                Ok(self.key.clone())
3158            }
3159        }
3160        let resolver = LegacyRecipient {
3161            recipient_calls: Cell::new(0),
3162            key: vec![0x63; 16],
3163        };
3164        let encrypted = encrypted_data_with_recipients(
3165            &resolver.key,
3166            vec![associated_encrypted_key("recipient", None, None)],
3167            None,
3168        );
3169        assert!(matches!(
3170            DecryptContext::new(&resolver).decrypt_data(&encrypted),
3171            Err(XmlEncError::KeyNotFound)
3172        ));
3173        assert_eq!(resolver.recipient_calls.get(), 0);
3174
3175        let direct = SymmetricKeyDecryptor::new(resolver.key.clone());
3176        let mut budget = KeyCandidateBudget::with_limit(1);
3177        assert!(matches!(
3178            resolver.resolve_key_candidates_with_policy(
3179                crate::provider::default_provider(),
3180                DataEncryptionAlgorithm::Aes128Gcm,
3181                encrypted.encrypted_keys.first(),
3182                &crate::policy::DecryptionPolicy::default(),
3183                &mut budget,
3184            ),
3185            Err(XmlEncError::KeyNotFound)
3186        ));
3187        assert_eq!(budget.remaining(), 1, "no recipient work was performed");
3188        assert_eq!(
3189            DecryptContext::new(&direct)
3190                .decrypt_data(&encrypted)
3191                .expect("direct AES does not require RSA metadata"),
3192            DecryptedContent::Bytes(b"payload".to_vec())
3193        );
3194    }
3195
3196    #[test]
3197    fn document_decryption_initial_parse_uses_the_policy_work_budget() {
3198        // Candidate retries and replacement validation must inherit the same
3199        // allowance consumed by the caller document's initial parse.
3200        let xml = "<root/>";
3201        let policy = crate::policy::DecryptionPolicy {
3202            resources: crate::policy::ResourcePolicy {
3203                max_xml_parse_work_bytes: 0,
3204                ..crate::policy::ResourcePolicy::default()
3205            },
3206            ..crate::policy::DecryptionPolicy::default()
3207        };
3208        let resolver = SymmetricKeyDecryptor::new([0_u8; 16]);
3209
3210        let error = DecryptContext::new(&resolver)
3211            .policy(policy)
3212            .decrypt_document(xml, None)
3213            .expect_err("a zero parse-work budget must reject the input parse");
3214
3215        assert!(matches!(
3216            error,
3217            XmlEncError::Policy(crate::policy::PolicyViolation::ResourceLimit {
3218                resource: crate::policy::resource_name::XML_PARSE_WORK_BYTES,
3219                maximum: 0,
3220                actual,
3221            }) if actual == xml.len()
3222        ));
3223    }
3224
3225    struct AggregateRecipientResolver {
3226        attempts: Cell<usize>,
3227        key: Vec<u8>,
3228    }
3229
3230    struct AssociationRecordingResolver {
3231        visited: RefCell<Vec<String>>,
3232        key: Vec<u8>,
3233    }
3234
3235    struct OrderedRecipientResolver {
3236        wrong: Vec<u8>,
3237        correct: Vec<u8>,
3238    }
3239
3240    struct DirectAndRecipientResolver {
3241        direct: Vec<u8>,
3242        recipient: Vec<u8>,
3243    }
3244
3245    struct FailingDirectResolver {
3246        recipient: Vec<u8>,
3247    }
3248
3249    struct PolicyRejectingDirectResolver {
3250        recipient: Vec<u8>,
3251    }
3252
3253    struct MislabelledExhaustionResolver {
3254        direct: Vec<u8>,
3255    }
3256
3257    impl DecryptionKeyResolver for DirectAndRecipientResolver {
3258        fn resolve_key_candidates_with_policy(
3259            &self,
3260            provider: &dyn crate::provider::CryptoProvider,
3261            algorithm: DataEncryptionAlgorithm,
3262            encrypted_key: Option<&EncryptedKey>,
3263            policy: &crate::policy::DecryptionPolicy,
3264            budget: &mut KeyCandidateBudget,
3265        ) -> Result<Vec<Vec<u8>>, XmlEncError> {
3266            // Prepared test keys simulate source ordering, not RSA recovery.
3267            policy.validate()?;
3268            self.resolve_key_candidates(provider, algorithm, encrypted_key, budget)
3269        }
3270
3271        fn resolve_key(
3272            &self,
3273            _provider: &dyn crate::provider::CryptoProvider,
3274            _algorithm: DataEncryptionAlgorithm,
3275            encrypted_key: Option<&EncryptedKey>,
3276        ) -> Result<Vec<u8>, XmlEncError> {
3277            Ok(if encrypted_key.is_some() {
3278                self.recipient.clone()
3279            } else {
3280                self.direct.clone()
3281            })
3282        }
3283    }
3284
3285    impl DecryptionKeyResolver for FailingDirectResolver {
3286        fn resolve_key_candidates_with_policy(
3287            &self,
3288            provider: &dyn crate::provider::CryptoProvider,
3289            algorithm: DataEncryptionAlgorithm,
3290            encrypted_key: Option<&EncryptedKey>,
3291            policy: &crate::policy::DecryptionPolicy,
3292            budget: &mut KeyCandidateBudget,
3293        ) -> Result<Vec<Vec<u8>>, XmlEncError> {
3294            // Prepared test keys simulate lookup errors, not RSA recovery.
3295            policy.validate()?;
3296            self.resolve_key_candidates(provider, algorithm, encrypted_key, budget)
3297        }
3298
3299        fn resolve_key(
3300            &self,
3301            _provider: &dyn crate::provider::CryptoProvider,
3302            algorithm: DataEncryptionAlgorithm,
3303            encrypted_key: Option<&EncryptedKey>,
3304        ) -> Result<Vec<u8>, XmlEncError> {
3305            if encrypted_key.is_some() {
3306                Ok(self.recipient.clone())
3307            } else {
3308                Err(XmlEncError::InvalidKeySize {
3309                    algorithm,
3310                    expected: 16,
3311                    actual: 8,
3312                })
3313            }
3314        }
3315    }
3316
3317    impl DecryptionKeyResolver for PolicyRejectingDirectResolver {
3318        fn resolve_key(
3319            &self,
3320            _provider: &dyn crate::provider::CryptoProvider,
3321            _algorithm: DataEncryptionAlgorithm,
3322            encrypted_key: Option<&EncryptedKey>,
3323        ) -> Result<Vec<u8>, XmlEncError> {
3324            if encrypted_key.is_some() {
3325                Ok(self.recipient.clone())
3326            } else {
3327                Err(crate::policy::PolicyViolation::KeyTrust {
3328                    reason: "test resolver rejected the operation",
3329                }
3330                .into())
3331            }
3332        }
3333    }
3334
3335    impl DecryptionKeyResolver for MislabelledExhaustionResolver {
3336        fn resolve_key(
3337            &self,
3338            _provider: &dyn crate::provider::CryptoProvider,
3339            _algorithm: DataEncryptionAlgorithm,
3340            _encrypted_key: Option<&EncryptedKey>,
3341        ) -> Result<Vec<u8>, XmlEncError> {
3342            Err(XmlEncError::KeyNotFound)
3343        }
3344
3345        fn resolve_key_candidates_with_policy(
3346            &self,
3347            _provider: &dyn crate::provider::CryptoProvider,
3348            _algorithm: DataEncryptionAlgorithm,
3349            encrypted_key: Option<&EncryptedKey>,
3350            policy: &crate::policy::DecryptionPolicy,
3351            budget: &mut KeyCandidateBudget,
3352        ) -> Result<Vec<Vec<u8>>, XmlEncError> {
3353            // Prepared test keys exercise operation-wide candidate accounting.
3354            policy.validate()?;
3355            if encrypted_key.is_none() {
3356                budget.consume(1)?;
3357                return Ok(vec![self.direct.clone()]);
3358            }
3359            budget.consume(budget.remaining().saturating_add(1))?;
3360            unreachable!("candidate budget exhaustion must return first")
3361        }
3362    }
3363
3364    impl DecryptionKeyResolver for OrderedRecipientResolver {
3365        fn resolve_key(
3366            &self,
3367            _provider: &dyn crate::provider::CryptoProvider,
3368            _algorithm: DataEncryptionAlgorithm,
3369            _encrypted_key: Option<&EncryptedKey>,
3370        ) -> Result<Vec<u8>, XmlEncError> {
3371            Err(XmlEncError::KeyNotFound)
3372        }
3373
3374        fn resolve_key_candidates_with_policy(
3375            &self,
3376            _provider: &dyn crate::provider::CryptoProvider,
3377            _algorithm: DataEncryptionAlgorithm,
3378            encrypted_key: Option<&EncryptedKey>,
3379            policy: &crate::policy::DecryptionPolicy,
3380            budget: &mut KeyCandidateBudget,
3381        ) -> Result<Vec<Vec<u8>>, XmlEncError> {
3382            // Prepared test keys simulate authenticated candidate ordering.
3383            policy.validate()?;
3384            budget.consume(1)?;
3385            match encrypted_key.and_then(|key| key.id.as_deref()) {
3386                Some("first") => Ok(vec![self.wrong.clone()]),
3387                Some("second") => Ok(vec![self.correct.clone()]),
3388                _ => Err(XmlEncError::KeyNotFound),
3389            }
3390        }
3391    }
3392
3393    impl DecryptionKeyResolver for CandidateResolver {
3394        fn resolve_key(
3395            &self,
3396            _provider: &dyn crate::provider::CryptoProvider,
3397            _algorithm: DataEncryptionAlgorithm,
3398            _encrypted_key: Option<&EncryptedKey>,
3399        ) -> Result<Vec<u8>, XmlEncError> {
3400            Err(XmlEncError::KeyNotFound)
3401        }
3402
3403        fn resolve_key_candidates(
3404            &self,
3405            _provider: &dyn crate::provider::CryptoProvider,
3406            _algorithm: DataEncryptionAlgorithm,
3407            encrypted_key: Option<&EncryptedKey>,
3408            budget: &mut KeyCandidateBudget,
3409        ) -> Result<Vec<Vec<u8>>, XmlEncError> {
3410            if encrypted_key.is_none() {
3411                budget.consume(self.keys.len())?;
3412                Ok(self.keys.clone())
3413            } else {
3414                Err(XmlEncError::KeyNotFound)
3415            }
3416        }
3417    }
3418
3419    impl DecryptionKeyResolver for AggregateRecipientResolver {
3420        fn resolve_key(
3421            &self,
3422            _provider: &dyn crate::provider::CryptoProvider,
3423            _algorithm: DataEncryptionAlgorithm,
3424            _encrypted_key: Option<&EncryptedKey>,
3425        ) -> Result<Vec<u8>, XmlEncError> {
3426            Err(XmlEncError::KeyNotFound)
3427        }
3428
3429        fn resolve_key_candidates_with_policy(
3430            &self,
3431            _provider: &dyn crate::provider::CryptoProvider,
3432            _algorithm: DataEncryptionAlgorithm,
3433            encrypted_key: Option<&EncryptedKey>,
3434            policy: &crate::policy::DecryptionPolicy,
3435            budget: &mut KeyCandidateBudget,
3436        ) -> Result<Vec<Vec<u8>>, XmlEncError> {
3437            // Prepared test keys isolate aggregate work from RSA primitives.
3438            policy.validate()?;
3439            let encrypted_key = encrypted_key.ok_or(XmlEncError::KeyNotFound)?;
3440            let attempts = budget.remaining();
3441            if attempts == 0 {
3442                budget.consume(1)?;
3443            }
3444            budget.consume(attempts)?;
3445            self.attempts.set(self.attempts.get() + attempts);
3446            if encrypted_key.id.as_deref() == Some("first") {
3447                Err(XmlEncError::KeyNotFound)
3448            } else {
3449                Ok(vec![self.key.clone()])
3450            }
3451        }
3452    }
3453
3454    impl DecryptionKeyResolver for AssociationRecordingResolver {
3455        fn resolve_key(
3456            &self,
3457            _provider: &dyn crate::provider::CryptoProvider,
3458            _algorithm: DataEncryptionAlgorithm,
3459            _encrypted_key: Option<&EncryptedKey>,
3460        ) -> Result<Vec<u8>, XmlEncError> {
3461            Err(XmlEncError::KeyNotFound)
3462        }
3463
3464        fn resolve_key_candidates_with_policy(
3465            &self,
3466            _provider: &dyn crate::provider::CryptoProvider,
3467            _algorithm: DataEncryptionAlgorithm,
3468            encrypted_key: Option<&EncryptedKey>,
3469            policy: &crate::policy::DecryptionPolicy,
3470            budget: &mut KeyCandidateBudget,
3471        ) -> Result<Vec<Vec<u8>>, XmlEncError> {
3472            // Prepared test keys isolate XMLEnc association selection.
3473            policy.validate()?;
3474            let encrypted_key = encrypted_key.ok_or(XmlEncError::KeyNotFound)?;
3475            budget.consume(1)?;
3476            self.visited
3477                .borrow_mut()
3478                .push(encrypted_key.id.clone().unwrap_or_default());
3479            Ok(vec![self.key.clone()])
3480        }
3481    }
3482
3483    fn associated_encrypted_key(
3484        id: &str,
3485        data_reference: Option<&str>,
3486        carried_key_name: Option<&str>,
3487    ) -> EncryptedKey {
3488        EncryptedKey {
3489            id: Some(id.into()),
3490            sources: Default::default(),
3491            recipient: None,
3492            key_name: None,
3493            encryption_method: EncryptionMethod {
3494                chacha: None,
3495                algorithm: KeyTransportAlgorithm::RsaOaep11.uri().into(),
3496                key_size_bits: None,
3497                oaep_digest: None,
3498                mgf_algorithm: None,
3499                oaep_params: None,
3500            },
3501            cipher_data: CipherData::Value {
3502                value: STANDARD.encode([0_u8; 256]),
3503            },
3504            reference_list: data_reference.map(|uri| crate::xmlenc::ReferenceList {
3505                data_references: vec![uri.into()],
3506                key_references: Vec::new(),
3507            }),
3508            carried_key_name: carried_key_name.map(str::to_owned),
3509        }
3510    }
3511
3512    fn encrypted_data_with_recipients(
3513        key: &[u8],
3514        encrypted_keys: Vec<EncryptedKey>,
3515        key_name: Option<&str>,
3516    ) -> EncryptedData {
3517        EncryptedData {
3518            encapsulation_methods: Vec::new(),
3519            id: Some("target".into()),
3520            derived_keys: Vec::new(),
3521            agreement_methods: Vec::new(),
3522            encrypted_type: None,
3523            encryption_method: EncryptionMethod {
3524                chacha: None,
3525                algorithm: DataEncryptionAlgorithm::Aes128Gcm.uri().into(),
3526                key_size_bits: None,
3527                oaep_digest: None,
3528                mgf_algorithm: None,
3529                oaep_params: None,
3530            },
3531            key_name: key_name.map(str::to_owned),
3532            encrypted_keys,
3533            cipher_data: CipherData::Value {
3534                value: STANDARD.encode(
3535                    crate::provider::default_provider()
3536                        .encrypt_data(DataEncryptionAlgorithm::Aes128Gcm, key, b"payload")
3537                        .expect("test encryption must succeed"),
3538                ),
3539            },
3540        }
3541    }
3542
3543    #[derive(Debug, Default)]
3544    struct PermissiveUnwrapProvider {
3545        decrypt_calls: AtomicUsize,
3546        unwrap_calls: AtomicUsize,
3547        recover_calls: AtomicUsize,
3548        plaintext: Vec<u8>,
3549        candidate_plaintexts: Vec<Vec<u8>>,
3550    }
3551
3552    struct OpaqueRecoveryKey;
3553
3554    impl crate::provider::KeyRecoveryKey for OpaqueRecoveryKey {
3555        fn rsa_modulus_bits(&self) -> usize {
3556            2048
3557        }
3558        fn rsa_public_exponent(&self) -> Option<u64> {
3559            Some(65537)
3560        }
3561        fn ciphertext_len(&self) -> usize {
3562            256
3563        }
3564
3565        fn recover_with_provider(
3566            &self,
3567            _provider: &dyn crate::provider::CryptoProvider,
3568            _parameters: &RsaOaepParameters,
3569            _ciphertext: &[u8],
3570        ) -> Result<Vec<u8>, crate::provider::ProviderError> {
3571            panic!("custom provider must own recovery for its opaque key")
3572        }
3573    }
3574
3575    impl crate::provider::CryptoProvider for PermissiveUnwrapProvider {
3576        #[cfg(feature = "legacy-algorithms")]
3577        fn recover_pkcs1v15(
3578            &self,
3579            key: &dyn crate::provider::KeyRecoveryKey,
3580            ciphertext: &[u8],
3581            key_len: usize,
3582        ) -> Result<crate::provider::RecoveredContentKey, crate::provider::ProviderError> {
3583            key.recover_pkcs1v15(self, ciphertext, key_len)
3584        }
3585        fn name(&self) -> &'static str {
3586            "permissive-unwrap-test"
3587        }
3588
3589        fn supports(&self, capability: crate::provider::ProviderCapability<'_>) -> bool {
3590            crate::provider::CryptoProvider::supports(
3591                &crate::provider::RustCryptoProvider,
3592                capability,
3593            )
3594        }
3595
3596        fn fill_random(&self, output: &mut [u8]) -> Result<(), crate::provider::ProviderError> {
3597            crate::provider::CryptoProvider::fill_random(
3598                &crate::provider::RustCryptoProvider,
3599                output,
3600            )
3601        }
3602
3603        fn derive_key(
3604            &self,
3605            parameters: &crate::provider::KdfParameters<'_>,
3606            secret: &[u8],
3607        ) -> Result<Vec<u8>, crate::provider::ProviderError> {
3608            crate::provider::RustCryptoProvider.derive_key(parameters, secret)
3609        }
3610
3611        #[cfg(feature = "xmldsig")]
3612        fn digest(
3613            &self,
3614            algorithm: crate::xmldsig::DigestAlgorithm,
3615            data: &[u8],
3616        ) -> Result<Vec<u8>, crate::provider::ProviderError> {
3617            crate::provider::CryptoProvider::digest(
3618                &crate::provider::RustCryptoProvider,
3619                algorithm,
3620                data,
3621            )
3622        }
3623
3624        #[cfg(feature = "xmldsig")]
3625        fn sign(
3626            &self,
3627            key: &dyn crate::xmldsig::SigningKey,
3628            algorithm: crate::xmldsig::SignatureAlgorithm,
3629            data: &[u8],
3630        ) -> Result<Vec<u8>, crate::xmldsig::SigningKeyError> {
3631            crate::provider::CryptoProvider::sign(
3632                &crate::provider::RustCryptoProvider,
3633                key,
3634                algorithm,
3635                data,
3636            )
3637        }
3638
3639        #[cfg(feature = "xmldsig")]
3640        fn verify(
3641            &self,
3642            key: &dyn crate::xmldsig::VerifyingKey,
3643            algorithm: crate::xmldsig::SignatureAlgorithm,
3644            data: &[u8],
3645            signature: &[u8],
3646        ) -> Result<bool, crate::xmldsig::DsigError> {
3647            crate::provider::CryptoProvider::verify(
3648                &crate::provider::RustCryptoProvider,
3649                key,
3650                algorithm,
3651                data,
3652                signature,
3653            )
3654        }
3655
3656        fn encrypt_data(
3657            &self,
3658            algorithm: DataEncryptionAlgorithm,
3659            key: &[u8],
3660            plaintext: &[u8],
3661        ) -> Result<Vec<u8>, crate::provider::ProviderError> {
3662            crate::provider::CryptoProvider::encrypt_data(
3663                &crate::provider::RustCryptoProvider,
3664                algorithm,
3665                key,
3666                plaintext,
3667            )
3668        }
3669
3670        fn decrypt_data(
3671            &self,
3672            _algorithm: DataEncryptionAlgorithm,
3673            _key: &[u8],
3674            _ciphertext: &[u8],
3675        ) -> Result<Vec<u8>, crate::provider::ProviderError> {
3676            let index = self.decrypt_calls.fetch_add(1, Ordering::Relaxed);
3677            Ok(self
3678                .candidate_plaintexts
3679                .get(index)
3680                .unwrap_or(&self.plaintext)
3681                .clone())
3682        }
3683
3684        fn wrap_key(
3685            &self,
3686            algorithm: KeyWrapAlgorithm,
3687            kek: &[u8],
3688            key: &[u8],
3689        ) -> Result<Vec<u8>, crate::provider::ProviderError> {
3690            crate::provider::CryptoProvider::wrap_key(
3691                &crate::provider::RustCryptoProvider,
3692                algorithm,
3693                kek,
3694                key,
3695            )
3696        }
3697
3698        fn unwrap_key(
3699            &self,
3700            _algorithm: KeyWrapAlgorithm,
3701            _kek: &[u8],
3702            _wrapped: &[u8],
3703        ) -> Result<Vec<u8>, crate::provider::ProviderError> {
3704            self.unwrap_calls.fetch_add(1, Ordering::Relaxed);
3705            Ok(vec![0_u8; 16])
3706        }
3707
3708        fn transport_key(
3709            &self,
3710            key: &dyn crate::provider::KeyTransportKey,
3711            parameters: &RsaOaepParameters,
3712            plaintext: &[u8],
3713        ) -> Result<Vec<u8>, crate::provider::ProviderError> {
3714            crate::provider::CryptoProvider::transport_key(
3715                &crate::provider::RustCryptoProvider,
3716                key,
3717                parameters,
3718                plaintext,
3719            )
3720        }
3721
3722        fn recover_key(
3723            &self,
3724            _key: &dyn crate::provider::KeyRecoveryKey,
3725            _parameters: &RsaOaepParameters,
3726            _ciphertext: &[u8],
3727        ) -> Result<Vec<u8>, crate::provider::ProviderError> {
3728            self.recover_calls.fetch_add(1, Ordering::Relaxed);
3729            Ok(vec![0_u8; 16])
3730        }
3731    }
3732
3733    impl DecryptionKeyResolver for CountingResolver {
3734        fn resolve_encapsulation_content_keys_with_policy(
3735            &self,
3736            _: &dyn crate::provider::CryptoProvider,
3737            _: DataEncryptionAlgorithm,
3738            _: &crate::key_establishment::EncapsulationMechanism,
3739            _: &crate::policy::DecryptionPolicy,
3740            _: &mut KeyCandidateBudget,
3741        ) -> Result<Vec<crate::provider::RecoveredContentKey>, XmlEncError> {
3742            self.candidate_calls.set(self.candidate_calls.get() + 1);
3743            Ok(vec![crate::provider::RecoveredContentKey::confirmed(
3744                self.key.clone(),
3745            )])
3746        }
3747
3748        fn resolve_key_candidates_with_policy(
3749            &self,
3750            provider: &dyn crate::provider::CryptoProvider,
3751            algorithm: DataEncryptionAlgorithm,
3752            encrypted_key: Option<&EncryptedKey>,
3753            policy: &crate::policy::DecryptionPolicy,
3754            budget: &mut KeyCandidateBudget,
3755        ) -> Result<Vec<Vec<u8>>, XmlEncError> {
3756            // Prepared test keys isolate validation-before-lookup ordering.
3757            policy.validate()?;
3758            self.resolve_key_candidates(provider, algorithm, encrypted_key, budget)
3759        }
3760
3761        fn resolve_key(
3762            &self,
3763            _provider: &dyn crate::provider::CryptoProvider,
3764            _algorithm: DataEncryptionAlgorithm,
3765            encrypted_key: Option<&EncryptedKey>,
3766        ) -> Result<Vec<u8>, XmlEncError> {
3767            if encrypted_key.is_some() {
3768                self.candidate_calls.set(self.candidate_calls.get() + 1);
3769                Ok(self.key.clone())
3770            } else {
3771                Err(XmlEncError::KeyNotFound)
3772            }
3773        }
3774    }
3775
3776    impl DecryptionKeyResolver for AllCallsResolver {
3777        fn resolve_key(
3778            &self,
3779            _provider: &dyn crate::provider::CryptoProvider,
3780            _algorithm: DataEncryptionAlgorithm,
3781            _encrypted_key: Option<&EncryptedKey>,
3782        ) -> Result<Vec<u8>, XmlEncError> {
3783            self.calls.set(self.calls.get() + 1);
3784            Ok(self.key.clone())
3785        }
3786    }
3787
3788    impl DecryptionKeyResolver for RecipientKeyResolver {
3789        fn resolve_key_candidates_with_policy(
3790            &self,
3791            provider: &dyn crate::provider::CryptoProvider,
3792            algorithm: DataEncryptionAlgorithm,
3793            encrypted_key: Option<&EncryptedKey>,
3794            policy: &crate::policy::DecryptionPolicy,
3795            budget: &mut KeyCandidateBudget,
3796        ) -> Result<Vec<Vec<u8>>, XmlEncError> {
3797            // Prepared test keys simulate recipient labels, not RSA recovery.
3798            policy.validate()?;
3799            self.resolve_key_candidates(provider, algorithm, encrypted_key, budget)
3800        }
3801
3802        fn resolve_key(
3803            &self,
3804            _provider: &dyn crate::provider::CryptoProvider,
3805            _algorithm: DataEncryptionAlgorithm,
3806            encrypted_key: Option<&EncryptedKey>,
3807        ) -> Result<Vec<u8>, XmlEncError> {
3808            if encrypted_key.and_then(|key| key.recipient.as_deref()) == Some(self.recipient) {
3809                Ok(self.key.clone())
3810            } else {
3811                Err(XmlEncError::KeyNotFound)
3812            }
3813        }
3814    }
3815
3816    #[test]
3817    fn decrypts_gcm_and_rejects_tampering() {
3818        // Authentication must cover the complete ciphertext and tag before plaintext returns.
3819        let key = [7_u8; 16];
3820        let nonce = [9_u8; 12];
3821        let mut ciphertext = b"<Assertion>trusted</Assertion>".to_vec();
3822        Aes128Gcm::new_from_slice(&key)
3823            .expect("fixed key length")
3824            .encrypt_in_place(&nonce.into(), b"", &mut ciphertext)
3825            .expect("test encryption must succeed");
3826        let mut wire = nonce.to_vec();
3827        wire.extend_from_slice(&ciphertext);
3828        let xml = format!(
3829            "<xenc:EncryptedData xmlns:xenc=\"http://www.w3.org/2001/04/xmlenc#\" Type=\"http://www.w3.org/2001/04/xmlenc#Element\"><xenc:EncryptionMethod Algorithm=\"http://www.w3.org/2009/xmlenc11#aes128-gcm\"/><xenc:CipherData><xenc:CipherValue>{}</xenc:CipherValue></xenc:CipherData></xenc:EncryptedData>",
3830            STANDARD.encode(&wire)
3831        );
3832        let decrypted = decrypt(&xml, &SymmetricKeyDecryptor::new(key))
3833            .expect("valid AES-GCM XML must decrypt");
3834        assert_eq!(
3835            decrypted,
3836            DecryptedContent::Xml("<Assertion>trusted</Assertion>".into())
3837        );
3838        let last = wire.len() - 1;
3839        wire[last] ^= 1;
3840        let tampered = xml.replace(&STANDARD.encode(ciphertext), &STANDARD.encode(&wire[12..]));
3841        assert!(matches!(
3842            decrypt(&tampered, &SymmetricKeyDecryptor::new(key)),
3843            Err(XmlEncError::AeadAuthenticationFailed)
3844        ));
3845    }
3846
3847    #[test]
3848    fn candidate_keys_retry_only_authenticated_decryption() {
3849        // Candidate selection belongs inside one prepared decryption operation:
3850        // structural validation and ciphertext decoding must not be repeated.
3851        let key = [7_u8; 16];
3852        let nonce = [9_u8; 12];
3853        let mut ciphertext = b"candidate plaintext".to_vec();
3854        Aes128Gcm::new_from_slice(&key)
3855            .expect("fixed key length")
3856            .encrypt_in_place(&nonce.into(), b"", &mut ciphertext)
3857            .expect("test encryption must succeed");
3858        let mut wire = nonce.to_vec();
3859        wire.extend_from_slice(&ciphertext);
3860        let encrypted = EncryptedData {
3861            encapsulation_methods: Vec::new(),
3862            id: None,
3863            derived_keys: Vec::new(),
3864            agreement_methods: Vec::new(),
3865            encrypted_type: None,
3866            encryption_method: EncryptionMethod {
3867                chacha: None,
3868                algorithm: DataEncryptionAlgorithm::Aes128Gcm.uri().into(),
3869                key_size_bits: None,
3870                oaep_digest: None,
3871                mgf_algorithm: None,
3872                oaep_params: None,
3873            },
3874            key_name: None,
3875            encrypted_keys: Vec::new(),
3876            cipher_data: CipherData::Value {
3877                value: STANDARD.encode(wire),
3878            },
3879        };
3880        let resolver = CandidateResolver {
3881            keys: vec![vec![1_u8; 16], key.to_vec()],
3882        };
3883
3884        let decrypted = DecryptContext::new(&resolver)
3885            .decrypt_data(&encrypted)
3886            .expect("a later authenticated candidate must decrypt");
3887
3888        assert_eq!(
3889            decrypted,
3890            DecryptedContent::Bytes(b"candidate plaintext".to_vec())
3891        );
3892    }
3893
3894    #[test]
3895    fn standalone_decryption_stops_after_first_successful_candidate() {
3896        // A successful authenticated candidate is the final standalone result;
3897        // later keys must not cause redundant decryptions or retained plaintexts.
3898        let provider = PermissiveUnwrapProvider {
3899            plaintext: b"accepted".to_vec(),
3900            ..PermissiveUnwrapProvider::default()
3901        };
3902        let resolver = CandidateResolver {
3903            keys: vec![vec![1_u8; 16], vec![2_u8; 16], vec![3_u8; 16]],
3904        };
3905        let encrypted = EncryptedData {
3906            encapsulation_methods: Vec::new(),
3907            id: None,
3908            derived_keys: Vec::new(),
3909            agreement_methods: Vec::new(),
3910            encrypted_type: None,
3911            encryption_method: EncryptionMethod {
3912                chacha: None,
3913                algorithm: DataEncryptionAlgorithm::Aes128Gcm.uri().into(),
3914                key_size_bits: None,
3915                oaep_digest: None,
3916                mgf_algorithm: None,
3917                oaep_params: None,
3918            },
3919            key_name: None,
3920            encrypted_keys: Vec::new(),
3921            cipher_data: CipherData::Value {
3922                value: STANDARD.encode([0_u8; 36]),
3923            },
3924        };
3925
3926        let result = DecryptContext::new(&resolver)
3927            .provider(&provider)
3928            .decrypt_data(&encrypted)
3929            .expect("the first successful candidate must be returned");
3930
3931        assert_eq!(result, DecryptedContent::Bytes(b"accepted".to_vec()));
3932        assert_eq!(provider.decrypt_calls.load(Ordering::Relaxed), 1);
3933    }
3934
3935    #[test]
3936    fn document_decryption_discards_rejected_plaintext_before_next_candidate() {
3937        // Replacement validation may reject authenticated plaintext. The next
3938        // key is then tried, but candidates after the first valid XML stay unused.
3939        let provider = PermissiveUnwrapProvider {
3940            candidate_plaintexts: vec![b"<bad".to_vec(), b"<x/>".to_vec(), b"<u/>".to_vec()],
3941            ..PermissiveUnwrapProvider::default()
3942        };
3943        let resolver = CandidateResolver {
3944            keys: vec![vec![1_u8; 16], vec![2_u8; 16], vec![3_u8; 16]],
3945        };
3946        let encrypted = format!(
3947            "<xenc:EncryptedData xmlns:xenc=\"{XMLENC_NS}\" Type=\"{XMLENC_NS}Element\"><xenc:EncryptionMethod Algorithm=\"{}\"/><xenc:CipherData><xenc:CipherValue>{}</xenc:CipherValue></xenc:CipherData></xenc:EncryptedData>",
3948            DataEncryptionAlgorithm::Aes128Gcm.uri(),
3949            STANDARD.encode([0_u8; 32]),
3950        );
3951
3952        let result = DecryptContext::new(&resolver)
3953            .provider(&provider)
3954            .decrypt_document(&encrypted, None)
3955            .expect("a later candidate with valid replacement XML must succeed");
3956
3957        assert_eq!(result, "<x/>");
3958        assert_eq!(provider.decrypt_calls.load(Ordering::Relaxed), 2);
3959    }
3960
3961    #[test]
3962    fn cbc_rejects_multiple_unordered_key_candidates() {
3963        // CBC padding cannot authenticate which candidate key is correct. A
3964        // resolver must select one key from trusted metadata before decryption.
3965        let encrypted = EncryptedData {
3966            encapsulation_methods: Vec::new(),
3967            id: None,
3968            derived_keys: Vec::new(),
3969            agreement_methods: Vec::new(),
3970            encrypted_type: None,
3971            encryption_method: EncryptionMethod {
3972                chacha: None,
3973                algorithm: DataEncryptionAlgorithm::Aes128Cbc.uri().into(),
3974                key_size_bits: None,
3975                oaep_digest: None,
3976                mgf_algorithm: None,
3977                oaep_params: None,
3978            },
3979            key_name: None,
3980            encrypted_keys: Vec::new(),
3981            cipher_data: CipherData::Value {
3982                value: STANDARD.encode(
3983                    crate::provider::default_provider()
3984                        .encrypt_data(
3985                            DataEncryptionAlgorithm::Aes128Cbc,
3986                            &[7_u8; 16],
3987                            b"opaque plaintext",
3988                        )
3989                        .expect("test encryption must succeed"),
3990                ),
3991            },
3992        };
3993        let resolver = CandidateResolver {
3994            keys: vec![vec![1_u8; 16], vec![7_u8; 16]],
3995        };
3996
3997        let error = DecryptContext::new(&resolver)
3998            .decrypt_data(&encrypted)
3999            .expect_err("unauthenticated CBC must not guess among candidate keys");
4000
4001        assert!(matches!(
4002            error,
4003            XmlEncError::AmbiguousKeyCandidates {
4004                algorithm: DataEncryptionAlgorithm::Aes128Cbc,
4005                actual: 2,
4006            }
4007        ));
4008    }
4009
4010    #[test]
4011    fn cbc_accepts_duplicate_copies_of_one_key_identity() {
4012        // Repeated sources containing the same key do not create the ambiguity
4013        // that unauthenticated CBC must reject between distinct key identities.
4014        let key = vec![0x27_u8; 16];
4015        let encrypted = EncryptedData {
4016            encapsulation_methods: Vec::new(),
4017            id: None,
4018            derived_keys: Vec::new(),
4019            agreement_methods: Vec::new(),
4020            encrypted_type: None,
4021            encryption_method: EncryptionMethod {
4022                chacha: None,
4023                algorithm: DataEncryptionAlgorithm::Aes128Cbc.uri().into(),
4024                key_size_bits: None,
4025                oaep_digest: None,
4026                mgf_algorithm: None,
4027                oaep_params: None,
4028            },
4029            key_name: None,
4030            encrypted_keys: Vec::new(),
4031            cipher_data: CipherData::Value {
4032                value: STANDARD.encode(
4033                    crate::provider::default_provider()
4034                        .encrypt_data(
4035                            DataEncryptionAlgorithm::Aes128Cbc,
4036                            &key,
4037                            b"duplicate identity",
4038                        )
4039                        .expect("test encryption must succeed"),
4040                ),
4041            },
4042        };
4043        let resolver = CandidateResolver {
4044            keys: vec![key.clone(), key],
4045        };
4046
4047        assert_eq!(
4048            DecryptContext::new(&resolver)
4049                .decrypt_data(&encrypted)
4050                .expect("one distinct CBC key identity must decrypt"),
4051            DecryptedContent::Bytes(b"duplicate identity".to_vec())
4052        );
4053    }
4054
4055    #[test]
4056    fn candidate_keys_are_bounded_before_cryptographic_processing() {
4057        // A resolver is caller-controlled; its result cannot multiply one
4058        // prepared operation beyond the implementation safety ceiling.
4059        let encrypted = EncryptedData {
4060            encapsulation_methods: Vec::new(),
4061            id: None,
4062            derived_keys: Vec::new(),
4063            agreement_methods: Vec::new(),
4064            encrypted_type: None,
4065            encryption_method: EncryptionMethod {
4066                chacha: None,
4067                algorithm: DataEncryptionAlgorithm::Aes128Gcm.uri().into(),
4068                key_size_bits: None,
4069                oaep_digest: None,
4070                mgf_algorithm: None,
4071                oaep_params: None,
4072            },
4073            key_name: None,
4074            encrypted_keys: Vec::new(),
4075            cipher_data: CipherData::Value {
4076                value: STANDARD.encode(vec![0_u8; 28]),
4077            },
4078        };
4079        let actual = crate::hard_limits::KEY_CANDIDATE_CEILING + 1;
4080        let resolver = CandidateResolver {
4081            keys: vec![vec![0_u8; 16]; actual],
4082        };
4083
4084        let error = DecryptContext::new(&resolver)
4085            .decrypt_data(&encrypted)
4086            .expect_err("oversized candidate sets must fail before decryption");
4087
4088        assert!(matches!(
4089            error,
4090            XmlEncError::Policy(crate::policy::PolicyViolation::ResourceLimit {
4091                resource: crate::policy::resource_name::KEY_CANDIDATES,
4092                maximum: crate::hard_limits::KEY_CANDIDATE_CEILING,
4093                actual: observed,
4094            }) if observed == actual
4095        ));
4096    }
4097
4098    #[test]
4099    fn operation_policy_controls_candidate_budget() {
4100        // A deployment-selected ceiling must reach resolver accounting; the
4101        // hard implementation ceiling is not the effective runtime policy.
4102        let encrypted = EncryptedData {
4103            encapsulation_methods: Vec::new(),
4104            id: None,
4105            derived_keys: Vec::new(),
4106            agreement_methods: Vec::new(),
4107            encrypted_type: None,
4108            encryption_method: EncryptionMethod {
4109                chacha: None,
4110                algorithm: DataEncryptionAlgorithm::Aes128Gcm.uri().into(),
4111                key_size_bits: None,
4112                oaep_digest: None,
4113                mgf_algorithm: None,
4114                oaep_params: None,
4115            },
4116            key_name: None,
4117            encrypted_keys: Vec::new(),
4118            cipher_data: CipherData::Value {
4119                value: STANDARD.encode(vec![0_u8; 28]),
4120            },
4121        };
4122        let resolver = CandidateResolver {
4123            keys: vec![vec![0_u8; 16]; 3],
4124        };
4125        let mut policy = crate::policy::DecryptionPolicy::default();
4126        policy.resources.max_key_candidates = 2;
4127
4128        let error = DecryptContext::new(&resolver)
4129            .policy(policy)
4130            .decrypt_data(&encrypted)
4131            .expect_err("candidate accounting must use the operation policy ceiling");
4132
4133        assert!(matches!(
4134            error,
4135            XmlEncError::Policy(crate::policy::PolicyViolation::ResourceLimit {
4136                resource: crate::policy::resource_name::KEY_CANDIDATES,
4137                maximum: 2,
4138                actual: 3,
4139            })
4140        ));
4141    }
4142
4143    #[test]
4144    fn candidate_work_ceiling_is_shared_across_recipients() {
4145        // The candidate ceiling bounds the complete decryption operation, not
4146        // each EncryptedKey independently.
4147        let key = vec![0x39_u8; 16];
4148        let resolver = AggregateRecipientResolver {
4149            attempts: Cell::new(0),
4150            key: key.clone(),
4151        };
4152        let encrypted = encrypted_data_with_recipients(
4153            &key,
4154            vec![
4155                associated_encrypted_key("first", None, None),
4156                associated_encrypted_key("second", None, None),
4157            ],
4158            None,
4159        );
4160
4161        DecryptContext::new(&resolver)
4162            .decrypt_data(&encrypted)
4163            .expect_err("a second recipient must not receive a fresh candidate allowance");
4164        assert_eq!(
4165            resolver.attempts.get(),
4166            crate::hard_limits::KEY_CANDIDATE_CEILING
4167        );
4168    }
4169
4170    #[test]
4171    fn candidate_budget_exhaustion_is_fatal_after_a_key_was_found() {
4172        // Candidate exhaustion must remain terminal even when it occurs in a
4173        // resolver path dedicated to one particular key family.
4174        let key = vec![0x49_u8; 16];
4175        let resolver = MislabelledExhaustionResolver {
4176            direct: key.clone(),
4177        };
4178        let encrypted = encrypted_data_with_recipients(
4179            &key,
4180            vec![associated_encrypted_key("recipient", None, None)],
4181            None,
4182        );
4183
4184        let error = DecryptContext::new(&resolver)
4185            .decrypt_data(&encrypted)
4186            .expect_err("candidate exhaustion must override an earlier usable key");
4187
4188        assert!(matches!(
4189            error,
4190            XmlEncError::Policy(crate::policy::PolicyViolation::ResourceLimit {
4191                resource: "key candidates",
4192                maximum: crate::hard_limits::KEY_CANDIDATE_CEILING,
4193                actual,
4194            }) if actual == crate::hard_limits::KEY_CANDIDATE_CEILING + 1
4195        ));
4196    }
4197
4198    #[test]
4199    fn authenticated_decryption_continues_after_wrong_unwrapped_recipient_key() {
4200        // A same-width key can unwrap successfully yet fail GCM authentication;
4201        // later applicable recipients must remain available to the data cipher.
4202        let correct = vec![0x53_u8; 16];
4203        let encrypted = encrypted_data_with_recipients(
4204            &correct,
4205            vec![
4206                associated_encrypted_key("first", None, None),
4207                associated_encrypted_key("second", None, None),
4208            ],
4209            None,
4210        );
4211        let resolver = OrderedRecipientResolver {
4212            wrong: vec![0x11_u8; 16],
4213            correct,
4214        };
4215
4216        let plaintext = DecryptContext::new(&resolver)
4217            .decrypt_data(&encrypted)
4218            .expect("the second recipient key must authenticate");
4219        assert_eq!(plaintext, DecryptedContent::Bytes(b"payload".to_vec()));
4220    }
4221
4222    #[test]
4223    fn authenticated_decryption_continues_from_direct_key_to_recipient() {
4224        // Direct candidates and embedded recipients are one ordered lookup
4225        // space; a wrong direct GCM key must not hide a valid wrapped key.
4226        let correct = vec![0x63_u8; 16];
4227        let encrypted = encrypted_data_with_recipients(
4228            &correct,
4229            vec![associated_encrypted_key("recipient", None, None)],
4230            None,
4231        );
4232        let resolver = DirectAndRecipientResolver {
4233            direct: vec![0x19_u8; 16],
4234            recipient: correct,
4235        };
4236
4237        let plaintext = DecryptContext::new(&resolver)
4238            .decrypt_data(&encrypted)
4239            .expect("the embedded recipient must remain available after a direct candidate");
4240
4241        assert_eq!(plaintext, DecryptedContent::Bytes(b"payload".to_vec()));
4242    }
4243
4244    #[test]
4245    fn authenticated_decryption_continues_after_direct_lookup_error() {
4246        // A candidate-local direct lookup failure must not suppress a valid
4247        // embedded recipient from the same ordered key-resolution operation.
4248        let correct = vec![0x64_u8; 16];
4249        let encrypted = encrypted_data_with_recipients(
4250            &correct,
4251            vec![associated_encrypted_key("recipient", None, None)],
4252            None,
4253        );
4254        let resolver = FailingDirectResolver { recipient: correct };
4255
4256        let plaintext = DecryptContext::new(&resolver)
4257            .decrypt_data(&encrypted)
4258            .expect("recipient lookup must follow a candidate-local direct error");
4259
4260        assert_eq!(plaintext, DecryptedContent::Bytes(b"payload".to_vec()));
4261    }
4262
4263    #[test]
4264    fn resolver_policy_rejection_stops_before_later_recipient() {
4265        // A custom resolver's typed policy rejection is operation-wide; trying
4266        // a later recipient after it would allow key-source policy bypass.
4267        let correct = vec![0x65_u8; 16];
4268        let encrypted = encrypted_data_with_recipients(
4269            &correct,
4270            vec![associated_encrypted_key("recipient", None, None)],
4271            None,
4272        );
4273        let resolver = PolicyRejectingDirectResolver { recipient: correct };
4274
4275        let error = DecryptContext::new(&resolver)
4276            .decrypt_data(&encrypted)
4277            .expect_err("operation policy rejection must be fatal");
4278
4279        assert!(matches!(
4280            error,
4281            XmlEncError::Policy(crate::policy::PolicyViolation::KeyTrust {
4282                reason: "test resolver rejected the operation",
4283            })
4284        ));
4285    }
4286
4287    #[test]
4288    fn cbc_rejects_distinct_direct_and_recipient_candidates() {
4289        // Combining lookup sources must not make unauthenticated CBC choose the
4290        // direct key merely because it was resolved before the recipient key.
4291        let recipient = vec![0x73_u8; 16];
4292        let mut encrypted = encrypted_data_with_recipients(
4293            &recipient,
4294            vec![associated_encrypted_key("recipient", None, None)],
4295            None,
4296        );
4297        encrypted.encryption_method.algorithm = DataEncryptionAlgorithm::Aes128Cbc.uri().into();
4298        *encrypted
4299            .cipher_data
4300            .inline_value_mut()
4301            .expect("inline ciphertext") = STANDARD.encode(
4302            crate::provider::default_provider()
4303                .encrypt_data(DataEncryptionAlgorithm::Aes128Cbc, &recipient, b"payload")
4304                .expect("test encryption must succeed"),
4305        );
4306        let resolver = DirectAndRecipientResolver {
4307            direct: vec![0x29_u8; 16],
4308            recipient,
4309        };
4310
4311        let error = DecryptContext::new(&resolver)
4312            .decrypt_data(&encrypted)
4313            .expect_err("CBC must not guess between direct and recipient keys");
4314
4315        assert!(matches!(
4316            error,
4317            XmlEncError::AmbiguousKeyCandidates {
4318                algorithm: DataEncryptionAlgorithm::Aes128Cbc,
4319                actual: 2,
4320            }
4321        ));
4322    }
4323
4324    #[test]
4325    fn cbc_ambiguity_ignores_algorithm_incompatible_key_widths() {
4326        // A wrong-width key cannot reach AES-CBC and therefore cannot make one
4327        // width-compatible candidate ambiguous.
4328        let key = vec![0x47_u8; 16];
4329        let ciphertext = crate::provider::default_provider()
4330            .encrypt_data(DataEncryptionAlgorithm::Aes128Cbc, &key, b"payload")
4331            .expect("test encryption must succeed");
4332        let encrypted = EncryptedData {
4333            encapsulation_methods: Vec::new(),
4334            id: None,
4335            derived_keys: Vec::new(),
4336            agreement_methods: Vec::new(),
4337            encrypted_type: None,
4338            encryption_method: EncryptionMethod {
4339                chacha: None,
4340                algorithm: DataEncryptionAlgorithm::Aes128Cbc.uri().into(),
4341                key_size_bits: None,
4342                oaep_digest: None,
4343                mgf_algorithm: None,
4344                oaep_params: None,
4345            },
4346            key_name: None,
4347            encrypted_keys: Vec::new(),
4348            cipher_data: CipherData::Value {
4349                value: STANDARD.encode(ciphertext),
4350            },
4351        };
4352        let resolver = CandidateResolver {
4353            keys: vec![vec![0_u8; 32], key],
4354        };
4355
4356        assert_eq!(
4357            DecryptContext::new(&resolver)
4358                .decrypt_data(&encrypted)
4359                .expect("the sole width-compatible CBC key must be selected"),
4360            DecryptedContent::Bytes(b"payload".to_vec())
4361        );
4362    }
4363
4364    #[test]
4365    fn data_reference_selects_the_associated_encrypted_key() {
4366        // An explicit DataReference to another object contradicts this
4367        // EncryptedData even when that recipient can be unwrapped.
4368        let key = vec![0x51_u8; 16];
4369        let resolver = AssociationRecordingResolver {
4370            visited: RefCell::new(Vec::new()),
4371            key: key.clone(),
4372        };
4373        let encrypted = encrypted_data_with_recipients(
4374            &key,
4375            vec![
4376                associated_encrypted_key("unrelated", Some("#other"), None),
4377                associated_encrypted_key("matching", Some("#target"), None),
4378            ],
4379            None,
4380        );
4381
4382        DecryptContext::new(&resolver)
4383            .decrypt_data(&encrypted)
4384            .expect("the associated recipient must decrypt");
4385        assert_eq!(resolver.visited.into_inner(), ["matching"]);
4386    }
4387
4388    #[test]
4389    fn carried_key_name_selects_the_named_content_key() {
4390        // CarriedKeyName identifies the transported key named by the enclosing
4391        // EncryptedData KeyInfo; a contradictory label must be skipped.
4392        let key = vec![0x52_u8; 16];
4393        let resolver = AssociationRecordingResolver {
4394            visited: RefCell::new(Vec::new()),
4395            key: key.clone(),
4396        };
4397        let encrypted = encrypted_data_with_recipients(
4398            &key,
4399            vec![
4400                associated_encrypted_key("unrelated", None, Some("other")),
4401                associated_encrypted_key("matching", None, Some("wanted")),
4402            ],
4403            Some("wanted"),
4404        );
4405
4406        DecryptContext::new(&resolver)
4407            .decrypt_data(&encrypted)
4408            .expect("the matching carried key name must decrypt");
4409        assert_eq!(resolver.visited.into_inner(), ["matching"]);
4410    }
4411
4412    #[test]
4413    fn contradictory_encrypted_key_associations_fail_closed() {
4414        // Association metadata is authoritative when present. The resolver must
4415        // not see a recipient that explicitly names another encrypted object.
4416        let key = vec![0x53_u8; 16];
4417        let resolver = AssociationRecordingResolver {
4418            visited: RefCell::new(Vec::new()),
4419            key: key.clone(),
4420        };
4421        let encrypted = encrypted_data_with_recipients(
4422            &key,
4423            vec![associated_encrypted_key("unrelated", Some("#other"), None)],
4424            None,
4425        );
4426
4427        assert!(matches!(
4428            DecryptContext::new(&resolver).decrypt_data(&encrypted),
4429            Err(XmlEncError::KeyNotFound)
4430        ));
4431        assert!(resolver.visited.into_inner().is_empty());
4432    }
4433
4434    #[test]
4435    fn direct_symmetric_key_ignores_embedded_key_hints() {
4436        // A caller-supplied content key is authoritative for this resolver;
4437        // unrelated recipient hints must not disable direct-key decryption.
4438        let key = [0x28_u8; 16];
4439        let unrelated = EncryptedKey {
4440            sources: Default::default(),
4441            id: None,
4442            recipient: Some("other-recipient".into()),
4443            key_name: None,
4444            encryption_method: super::super::EncryptionMethod {
4445                chacha: None,
4446                algorithm: "urn:unrelated:key-transport".into(),
4447                key_size_bits: None,
4448                oaep_digest: None,
4449                mgf_algorithm: None,
4450                oaep_params: None,
4451            },
4452            cipher_data: super::super::CipherData::Value {
4453                value: STANDARD.encode([0_u8; 24]),
4454            },
4455            reference_list: None,
4456            carried_key_name: None,
4457        };
4458
4459        assert_eq!(
4460            SymmetricKeyDecryptor::new(key)
4461                .resolve_key(
4462                    crate::provider::default_provider(),
4463                    DataEncryptionAlgorithm::Aes128Gcm,
4464                    Some(&unrelated)
4465                )
4466                .expect("direct key must ignore unrelated embedded hints"),
4467            key
4468        );
4469    }
4470
4471    #[test]
4472    fn decrypts_with_the_matching_recipient_key() {
4473        // Multi-recipient KeyInfo must retain document order and continue after a
4474        // malformed unrelated key before accepting the intended one.
4475        let key = [0x29_u8; 16];
4476        let plaintext = "recipient-specific plaintext";
4477        let encrypted = encrypted_gcm_element("", plaintext, None, true, &key);
4478        let recipient_key = |recipient: &str, method: &str| {
4479            format!(
4480                "<xenc:EncryptedKey Recipient=\"{recipient}\"><xenc:EncryptionMethod Algorithm=\"{method}\">{}</xenc:EncryptionMethod><xenc:CipherData><xenc:CipherValue>YQ==</xenc:CipherValue></xenc:CipherData></xenc:EncryptedKey>",
4481                if recipient == "alice" {
4482                    "<ds:DigestMethod Algorithm=\"urn:unsupported:digest\"/>"
4483                } else {
4484                    ""
4485                }
4486            )
4487        };
4488        let key_info = format!(
4489            "<ds:KeyInfo xmlns:ds=\"{}\">{}{}</ds:KeyInfo>",
4490            crate::xmlenc::types::XMLDSIG_NS,
4491            recipient_key("alice", KeyTransportAlgorithm::RsaOaep11.uri()),
4492            recipient_key("bob", KeyWrapAlgorithm::AesKw128.uri())
4493        );
4494        let xml = encrypted.replacen(
4495            "<xenc:CipherData>",
4496            &format!("{key_info}<xenc:CipherData>"),
4497            1,
4498        );
4499        let resolver = RecipientKeyResolver {
4500            recipient: "bob",
4501            key: key.to_vec(),
4502        };
4503
4504        assert_eq!(
4505            decrypt(&xml, &resolver).expect("second recipient key must be tried"),
4506            DecryptedContent::Bytes(plaintext.as_bytes().to_vec())
4507        );
4508    }
4509
4510    #[test]
4511    fn decryption_policy_bounds_recipients_before_key_resolution() {
4512        // Both XML parsing and caller-constructed typed input must reject an
4513        // oversized recipient set before any resolver can inspect candidates.
4514        let key = [0x29_u8; 16];
4515        let encrypted = encrypted_gcm_element("", "bounded recipients", None, true, &key);
4516        let recipient_key = |recipient: &str| {
4517            format!(
4518                "<xenc:EncryptedKey Recipient=\"{recipient}\"><xenc:EncryptionMethod Algorithm=\"urn:test:key\"/><xenc:CipherData><xenc:CipherValue>YQ==</xenc:CipherValue></xenc:CipherData></xenc:EncryptedKey>"
4519            )
4520        };
4521        let key_info = format!(
4522            "<ds:KeyInfo xmlns:ds=\"{}\">{}{}</ds:KeyInfo>",
4523            crate::xmlenc::types::XMLDSIG_NS,
4524            recipient_key("alice"),
4525            recipient_key("bob")
4526        );
4527        let xml = encrypted.replacen(
4528            "<xenc:CipherData>",
4529            &format!("{key_info}<xenc:CipherData>"),
4530            1,
4531        );
4532        let parsed = parse_encrypted_data(&xml).expect("default parser accepts two recipients");
4533        let policy = crate::policy::DecryptionPolicy {
4534            resources: crate::policy::ResourcePolicy {
4535                max_encryption_recipients: 1,
4536                ..crate::policy::ResourcePolicy::default()
4537            },
4538            ..crate::policy::DecryptionPolicy::default()
4539        };
4540        let resolver = SymmetricKeyDecryptor::new(key);
4541        let context = DecryptContext::new(&resolver).policy(policy);
4542
4543        for error in [
4544            context
4545                .decrypt(&xml)
4546                .expect_err("XML recipient collection must be bounded"),
4547            context
4548                .decrypt_data(&parsed)
4549                .expect_err("typed recipient collection must be bounded"),
4550        ] {
4551            assert!(matches!(
4552                error,
4553                XmlEncError::Policy(crate::policy::PolicyViolation::ResourceLimit {
4554                    resource: crate::policy::resource_name::ENCRYPTION_RECIPIENTS,
4555                    maximum: 1,
4556                    actual: 2,
4557                })
4558            ));
4559        }
4560    }
4561
4562    #[test]
4563    fn decrypts_session_key_wrapped_with_aes_kw() {
4564        // RFC 3394 unwrap must recover exactly the content algorithm's key length.
4565        let kek = [3_u8; 16];
4566        let session_key = [4_u8; 16];
4567        let mut wrapped = [0_u8; 24];
4568        KwAes128::new_from_slice(&kek)
4569            .expect("fixed KEK length")
4570            .wrap_key(&session_key, &mut wrapped)
4571            .expect("RFC 3394 test wrapping must succeed");
4572        let encrypted_key = EncryptedKey {
4573            id: None,
4574            sources: Default::default(),
4575            recipient: None,
4576            key_name: None,
4577            encryption_method: super::super::EncryptionMethod {
4578                chacha: None,
4579                algorithm: "http://www.w3.org/2001/04/xmlenc#kw-aes128".into(),
4580                key_size_bits: None,
4581                oaep_digest: None,
4582                mgf_algorithm: None,
4583                oaep_params: None,
4584            },
4585            cipher_data: super::super::CipherData::Value {
4586                value: STANDARD.encode(wrapped),
4587            },
4588            reference_list: None,
4589            carried_key_name: None,
4590        };
4591        let resolved = KekDecryptor::new(kek)
4592            .resolve_key(
4593                crate::provider::default_provider(),
4594                DataEncryptionAlgorithm::Aes128Gcm,
4595                Some(&encrypted_key),
4596            )
4597            .expect("wrapped session key must resolve");
4598        assert_eq!(resolved, session_key);
4599    }
4600
4601    #[test]
4602    fn rejects_invalid_kek_before_custom_provider_dispatch() {
4603        // KEK length is part of the XMLEnc algorithm contract, not a provider
4604        // preference. A permissive provider must not bypass facade validation.
4605        let encrypted_key = EncryptedKey {
4606            id: None,
4607            sources: Default::default(),
4608            recipient: None,
4609            key_name: None,
4610            encryption_method: super::super::EncryptionMethod {
4611                chacha: None,
4612                algorithm: KeyWrapAlgorithm::AesKw128.uri().into(),
4613                key_size_bits: None,
4614                oaep_digest: None,
4615                mgf_algorithm: None,
4616                oaep_params: None,
4617            },
4618            cipher_data: super::super::CipherData::Value {
4619                value: STANDARD.encode([0_u8; 24]),
4620            },
4621            reference_list: None,
4622            carried_key_name: None,
4623        };
4624        let provider = PermissiveUnwrapProvider::default();
4625
4626        assert!(matches!(
4627            KekDecryptor::new([0_u8; 32]).resolve_key(
4628                &provider,
4629                DataEncryptionAlgorithm::Aes128Gcm,
4630                Some(&encrypted_key),
4631            ),
4632            Err(XmlEncError::InvalidKekSize {
4633                algorithm: KeyWrapAlgorithm::AesKw128,
4634                expected: 16,
4635                actual: 32,
4636            })
4637        ));
4638        assert_eq!(provider.unwrap_calls.load(Ordering::Relaxed), 0);
4639    }
4640
4641    #[test]
4642    fn rejects_content_ciphertext_framing_before_resolution_or_provider_dispatch() {
4643        // Algorithm framing belongs to the XMLEnc facade. A permissive provider
4644        // and resolver must never observe malformed standard CipherValue bytes.
4645        for (algorithm, ciphertext_len) in [
4646            (DataEncryptionAlgorithm::Aes128Gcm, 27),
4647            (DataEncryptionAlgorithm::Aes128Cbc, 33),
4648        ] {
4649            let resolver = AllCallsResolver {
4650                calls: Cell::new(0),
4651                key: vec![0_u8; algorithm.key_len()],
4652            };
4653            let provider = PermissiveUnwrapProvider::default();
4654            let encrypted = EncryptedData {
4655                encapsulation_methods: Vec::new(),
4656                id: None,
4657                derived_keys: Vec::new(),
4658                agreement_methods: Vec::new(),
4659                encrypted_type: None,
4660                key_name: None,
4661                encryption_method: super::super::EncryptionMethod {
4662                    chacha: None,
4663                    algorithm: algorithm.uri().into(),
4664                    key_size_bits: None,
4665                    oaep_digest: None,
4666                    mgf_algorithm: None,
4667                    oaep_params: None,
4668                },
4669                encrypted_keys: Vec::new(),
4670                cipher_data: super::super::CipherData::Value {
4671                    value: STANDARD.encode(vec![0_u8; ciphertext_len]),
4672                },
4673            };
4674
4675            assert!(
4676                DecryptContext::new(&resolver)
4677                    .provider(&provider)
4678                    .decrypt_data(&encrypted)
4679                    .is_err()
4680            );
4681            assert_eq!(resolver.calls.get(), 0);
4682            assert_eq!(provider.decrypt_calls.load(Ordering::Relaxed), 0);
4683        }
4684    }
4685
4686    #[test]
4687    fn rejects_custom_provider_plaintext_outside_algorithm_bounds() {
4688        // A provider success result is still untrusted: GCM fixes the plaintext
4689        // length exactly, while CBC padding permits only one block-sized range.
4690        for (algorithm, ciphertext_len, plaintext_len) in [
4691            (DataEncryptionAlgorithm::Aes128Gcm, 32, 5),
4692            (DataEncryptionAlgorithm::Aes128Cbc, 32, 16),
4693        ] {
4694            let resolver = AllCallsResolver {
4695                calls: Cell::new(0),
4696                key: vec![0_u8; algorithm.key_len()],
4697            };
4698            let provider = PermissiveUnwrapProvider {
4699                plaintext: vec![0_u8; plaintext_len],
4700                ..PermissiveUnwrapProvider::default()
4701            };
4702            let encrypted = EncryptedData {
4703                encapsulation_methods: Vec::new(),
4704                id: None,
4705                derived_keys: Vec::new(),
4706                agreement_methods: Vec::new(),
4707                encrypted_type: None,
4708                key_name: None,
4709                encryption_method: super::super::EncryptionMethod {
4710                    chacha: None,
4711                    algorithm: algorithm.uri().into(),
4712                    key_size_bits: None,
4713                    oaep_digest: None,
4714                    mgf_algorithm: None,
4715                    oaep_params: None,
4716                },
4717                encrypted_keys: Vec::new(),
4718                cipher_data: super::super::CipherData::Value {
4719                    value: STANDARD.encode(vec![0_u8; ciphertext_len]),
4720                },
4721            };
4722
4723            let error = DecryptContext::new(&resolver)
4724                .provider(&provider)
4725                .decrypt_data(&encrypted)
4726                .expect_err("impossible provider output length must fail");
4727            match algorithm {
4728                DataEncryptionAlgorithm::Aes128Gcm => assert!(matches!(
4729                    error,
4730                    XmlEncError::Provider(crate::provider::ProviderError::InvalidOutputSize {
4731                        operation: crate::provider::ProviderOperation::Decrypt,
4732                        expected: 4,
4733                        actual: 5,
4734                    })
4735                )),
4736                DataEncryptionAlgorithm::Aes128Cbc => assert!(matches!(
4737                    error,
4738                    XmlEncError::Provider(crate::provider::ProviderError::InvalidOutputSizeRange {
4739                        operation: crate::provider::ProviderOperation::Decrypt,
4740                        minimum: 0,
4741                        maximum: 15,
4742                        actual: 16,
4743                    })
4744                )),
4745                _ => unreachable!("the regression table covers one GCM and one CBC algorithm"),
4746            }
4747            assert_eq!(provider.decrypt_calls.load(Ordering::Relaxed), 1);
4748        }
4749    }
4750
4751    #[test]
4752    fn rejects_malformed_aes_kw_before_custom_provider_dispatch() {
4753        // RFC 3394 adds exactly eight bytes to the transported content key;
4754        // permissive custom providers must not redefine that wire contract.
4755        let provider = PermissiveUnwrapProvider::default();
4756        for actual in [0, 23, 25] {
4757            let encrypted_key = EncryptedKey {
4758                sources: Default::default(),
4759                id: None,
4760                recipient: None,
4761                key_name: None,
4762                encryption_method: super::super::EncryptionMethod {
4763                    chacha: None,
4764                    algorithm: KeyWrapAlgorithm::AesKw128.uri().into(),
4765                    key_size_bits: None,
4766                    oaep_digest: None,
4767                    mgf_algorithm: None,
4768                    oaep_params: None,
4769                },
4770                cipher_data: super::super::CipherData::Value {
4771                    value: STANDARD.encode(vec![0_u8; actual]),
4772                },
4773                reference_list: None,
4774                carried_key_name: None,
4775            };
4776            assert!(matches!(
4777                KekDecryptor::new([0_u8; 16]).resolve_key(
4778                    &provider,
4779                    DataEncryptionAlgorithm::Aes128Gcm,
4780                    Some(&encrypted_key),
4781                ),
4782                Err(XmlEncError::InvalidWrappedKeyLength {
4783                    expected: 24,
4784                    actual: output_len,
4785                }) if output_len == actual
4786            ));
4787        }
4788        assert_eq!(provider.unwrap_calls.load(Ordering::Relaxed), 0);
4789    }
4790
4791    #[test]
4792    fn rejects_malformed_rsa_oaep_before_custom_provider_dispatch() {
4793        // RSA ciphertext width is the private modulus width, so malformed
4794        // transport bytes must be rejected before provider-owned recovery.
4795        let private_key = RsaPrivateKey::from_pkcs8_pem(include_str!(
4796            "../../tests/fixtures/keys/rsa/rsa-2048-key.pem"
4797        ))
4798        .expect("RSA donor private key must parse");
4799        let provider = PermissiveUnwrapProvider::default();
4800        for actual in [0, 255, 257] {
4801            assert!(matches!(
4802                recover_rsa_oaep(
4803                    &provider,
4804                    &private_key,
4805                    &RsaOaepParameters::default(),
4806                    &vec![0_u8; actual],
4807                    DataEncryptionAlgorithm::Aes128Gcm,
4808                ),
4809                Err(XmlEncError::InvalidWrappedKeyLength {
4810                    expected: 256,
4811                    actual: output_len,
4812                }) if output_len == actual
4813            ));
4814        }
4815        assert_eq!(provider.recover_calls.load(Ordering::Relaxed), 0);
4816    }
4817
4818    #[cfg(feature = "legacy-algorithms")]
4819    #[test]
4820    fn invalid_rsa_recovery_cannot_release_successful_cbc_plaintext() {
4821        // Force the CBC provider to accept its padding, making this regression
4822        // deterministic instead of waiting for random fallback plaintext.
4823        let key = RsaPrivateKey::from_pkcs8_pem(include_str!(
4824            "../../tests/fixtures/keys/rsa/rsa-2048-key.pem"
4825        ))
4826        .expect("valid RSA fixture");
4827        let resolver = PrivateKeyDecryptor::new(key);
4828        for algorithm in [
4829            DataEncryptionAlgorithm::Aes128Cbc,
4830            DataEncryptionAlgorithm::Aes192Cbc,
4831            DataEncryptionAlgorithm::Aes256Cbc,
4832            DataEncryptionAlgorithm::TripleDesCbc,
4833            DataEncryptionAlgorithm::Aes128Gcm,
4834        ] {
4835            let provider = PermissiveUnwrapProvider {
4836                plaintext: b"accepted CBC plaintext".to_vec(),
4837                ..PermissiveUnwrapProvider::default()
4838            };
4839            let mut encrypted = encrypted_data_with_recipients(
4840                &[0; 16],
4841                vec![associated_encrypted_key("recipient", None, None)],
4842                None,
4843            );
4844            encrypted.encryption_method.algorithm = algorithm.uri().into();
4845            *encrypted
4846                .cipher_data
4847                .inline_value_mut()
4848                .expect("inline ciphertext") =
4849                STANDARD.encode(vec![
4850                    0;
4851                    algorithm
4852                        .ciphertext_len_for_plaintext(provider.plaintext.len())
4853                        .expect("bounded test frame")
4854                ]);
4855            encrypted.encrypted_keys[0].encryption_method.algorithm =
4856                KeyTransportAlgorithm::RsaPkcs1v15.uri().into();
4857            *encrypted.encrypted_keys[0]
4858                .cipher_data
4859                .inline_value_mut()
4860                .expect("inline wrapped key") = STANDARD.encode([0; 256]);
4861            let result = DecryptContext::new(&resolver)
4862                .provider(&provider)
4863                .policy(crate::policy::DecryptionPolicy {
4864                    data_algorithms: Some([algorithm].into()),
4865                    key_transport_algorithms: Some([KeyTransportAlgorithm::RsaPkcs1v15].into()),
4866                    ..crate::policy::DecryptionPolicy::default()
4867                })
4868                .decrypt_data(&encrypted);
4869            assert!(
4870                result.is_err(),
4871                "invalid RSA padding must never release fallback plaintext"
4872            );
4873            assert_eq!(
4874                provider.decrypt_calls.load(Ordering::Relaxed),
4875                1,
4876                "content decryption must run before rejecting the recovery"
4877            );
4878        }
4879    }
4880
4881    #[test]
4882    fn candidate_filter_reuses_storage_and_preserves_precedence() {
4883        // Compact invalid widths and non-adjacent duplicates in place without
4884        // reordering distinct candidates or allocating a second candidate list.
4885        let keys = [
4886            vec![0; 8],
4887            vec![2; 16],
4888            vec![3; 16],
4889            vec![2; 16],
4890            vec![4; 32],
4891            vec![5; 16],
4892        ]
4893        .into_iter()
4894        .map(crate::provider::RecoveredContentKey::confirmed)
4895        .collect::<Vec<_>>();
4896        let allocation = keys.as_ptr();
4897        let keys = compatible_decryption_key_candidates(DataEncryptionAlgorithm::Aes128Gcm, keys)
4898            .expect("compatible candidates survive");
4899        assert_eq!(keys.as_ptr(), allocation);
4900        assert_eq!(keys.len(), 3);
4901        for (key, expected) in keys.iter().zip([2, 3, 5]) {
4902            assert_eq!(key.bytes(), &[expected; 16]);
4903        }
4904    }
4905
4906    #[test]
4907    fn custom_provider_recovers_with_an_opaque_private_key() {
4908        // The resolver knows only the public ciphertext width; private key
4909        // material remains entirely behind the provider/key-handle boundary.
4910        let encrypted_key = EncryptedKey {
4911            id: None,
4912            sources: Default::default(),
4913            recipient: None,
4914            key_name: None,
4915            encryption_method: super::super::EncryptionMethod {
4916                chacha: None,
4917                algorithm: KeyTransportAlgorithm::RsaOaep11.uri().into(),
4918                key_size_bits: None,
4919                oaep_digest: Some(OaepDigestAlgorithm::Sha256.uri().into()),
4920                mgf_algorithm: Some(
4921                    OaepDigestAlgorithm::Sha256
4922                        .mgf_uri()
4923                        .expect("MGF1-SHA256 URI")
4924                        .into(),
4925                ),
4926                oaep_params: None,
4927            },
4928            cipher_data: super::super::CipherData::Value {
4929                value: STANDARD.encode(vec![0x5a; 256]),
4930            },
4931            reference_list: None,
4932            carried_key_name: None,
4933        };
4934        let provider = PermissiveUnwrapProvider::default();
4935        let decryptor = PrivateKeyDecryptor::provider_key(Arc::new(OpaqueRecoveryKey));
4936
4937        let key = decryptor
4938            .resolve_key(
4939                &provider,
4940                DataEncryptionAlgorithm::Aes128Gcm,
4941                Some(&encrypted_key),
4942            )
4943            .expect("custom provider must recover through its opaque private key");
4944
4945        assert_eq!(key, vec![0_u8; 16]);
4946        assert_eq!(provider.recover_calls.load(Ordering::Relaxed), 1);
4947    }
4948
4949    #[test]
4950    fn rejects_truncated_gcm_and_invalid_wrapped_key() {
4951        // Framing and key-wrap integrity failures must occur before content is exposed.
4952        assert!(matches!(
4953            crate::provider::default_provider().decrypt_data(
4954                DataEncryptionAlgorithm::Aes128Gcm,
4955                &[0_u8; 16],
4956                &[0_u8; 27],
4957            ),
4958            Err(crate::provider::ProviderError::InvalidInput(
4959                crate::provider::ProviderInputError::AesGcmFraming
4960            ))
4961        ));
4962        let truncated = EncryptedData {
4963            encapsulation_methods: Vec::new(),
4964            id: None,
4965            derived_keys: Vec::new(),
4966            agreement_methods: Vec::new(),
4967            encrypted_type: None,
4968            key_name: None,
4969            encryption_method: super::super::EncryptionMethod {
4970                chacha: None,
4971                algorithm: DataEncryptionAlgorithm::Aes128Gcm.uri().into(),
4972                key_size_bits: None,
4973                oaep_digest: None,
4974                mgf_algorithm: None,
4975                oaep_params: None,
4976            },
4977            encrypted_keys: Vec::new(),
4978            cipher_data: super::super::CipherData::Value {
4979                value: STANDARD.encode([0_u8; 27]),
4980            },
4981        };
4982        assert!(matches!(
4983            DecryptContext::new(&SymmetricKeyDecryptor::new([0_u8; 16])).decrypt_data(&truncated),
4984            Err(XmlEncError::DataTooShort {
4985                algorithm: "AES-GCM",
4986                actual: 27,
4987                ..
4988            })
4989        ));
4990        let encrypted_key = EncryptedKey {
4991            id: None,
4992            sources: Default::default(),
4993            recipient: None,
4994            key_name: None,
4995            encryption_method: super::super::EncryptionMethod {
4996                chacha: None,
4997                algorithm: "http://www.w3.org/2001/04/xmlenc#kw-aes128".into(),
4998                key_size_bits: None,
4999                oaep_digest: None,
5000                mgf_algorithm: None,
5001                oaep_params: None,
5002            },
5003            cipher_data: super::super::CipherData::Value {
5004                value: STANDARD.encode([0_u8; 24]),
5005            },
5006            reference_list: None,
5007            carried_key_name: None,
5008        };
5009        assert!(matches!(
5010            KekDecryptor::new([0_u8; 16]).resolve_key(
5011                crate::provider::default_provider(),
5012                DataEncryptionAlgorithm::Aes128Gcm,
5013                Some(&encrypted_key)
5014            ),
5015            Err(XmlEncError::KeyWrapIntegrity)
5016        ));
5017        assert!(matches!(
5018            KekDecryptor::new([0_u8; 32]).resolve_key(
5019                crate::provider::default_provider(),
5020                DataEncryptionAlgorithm::Aes128Gcm,
5021                Some(&encrypted_key)
5022            ),
5023            Err(XmlEncError::InvalidKekSize {
5024                algorithm: KeyWrapAlgorithm::AesKw128,
5025                expected: 16,
5026                actual: 32
5027            })
5028        ));
5029    }
5030
5031    #[test]
5032    fn decrypts_oaep11_with_independent_digest_and_mgf() {
5033        // XMLEnc 1.1 permits the OAEP digest and MGF1 digest to differ.
5034        let private_key = RsaPrivateKey::from_pkcs8_pem(include_str!(
5035            "../../tests/fixtures/keys/rsa/rsa-2048-key.pem"
5036        ))
5037        .expect("RSA donor private key must parse");
5038        let public_key = RsaPublicKey::from(&private_key);
5039        let session_key = [6_u8; 16];
5040        let label = b"xmlenc-label".to_vec();
5041        let wrapped = public_key
5042            .encrypt(
5043                &mut ChaCha20Rng::from_seed([17_u8; 32]),
5044                Oaep::<Sha256, Sha384>::new_with_mgf_hash_and_label(label.clone()),
5045                &session_key,
5046            )
5047            .expect("OAEP test wrapping must succeed");
5048        let encrypted_key = EncryptedKey {
5049            id: Some("wrapped-key".into()),
5050            sources: Default::default(),
5051            recipient: Some("recipient-a".into()),
5052            key_name: None,
5053            encryption_method: super::super::EncryptionMethod {
5054                chacha: None,
5055                algorithm: "http://www.w3.org/2009/xmlenc11#rsa-oaep".into(),
5056                key_size_bits: None,
5057                oaep_digest: Some("http://www.w3.org/2001/04/xmlenc#sha256".into()),
5058                mgf_algorithm: Some("http://www.w3.org/2009/xmlenc11#mgf1sha384".into()),
5059                oaep_params: Some(label),
5060            },
5061            cipher_data: super::super::CipherData::Value {
5062                value: STANDARD.encode(wrapped),
5063            },
5064            reference_list: None,
5065            carried_key_name: None,
5066        };
5067        let resolved = PrivateKeyDecryptor::new(private_key)
5068            .resolve_key(
5069                crate::provider::default_provider(),
5070                DataEncryptionAlgorithm::Aes128Gcm,
5071                Some(&encrypted_key),
5072            )
5073            .expect("OAEP 1.1 wrapped key must resolve");
5074        assert_eq!(resolved, session_key);
5075    }
5076
5077    #[test]
5078    fn decrypts_legacy_oaep_uri_with_sha256_digest() {
5079        // The legacy URI defaults MGF1 to SHA-1 when no xenc11:MGF child is present.
5080        let private_key = RsaPrivateKey::from_pkcs8_pem(include_str!(
5081            "../../tests/fixtures/keys/rsa/rsa-2048-key.pem"
5082        ))
5083        .expect("RSA donor private key must parse");
5084        let public_key = RsaPublicKey::from(&private_key);
5085        let session_key = [8_u8; 16];
5086        let wrapped = public_key
5087            .encrypt(
5088                &mut ChaCha20Rng::from_seed([19_u8; 32]),
5089                Oaep::<Sha256, Sha1>::new_with_mgf_hash(),
5090                &session_key,
5091            )
5092            .expect("legacy OAEP URI test wrapping must succeed");
5093        let encrypted_key = EncryptedKey {
5094            id: None,
5095            sources: Default::default(),
5096            recipient: None,
5097            key_name: None,
5098            encryption_method: super::super::EncryptionMethod {
5099                chacha: None,
5100                algorithm: "http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p".into(),
5101                key_size_bits: None,
5102                oaep_digest: Some("http://www.w3.org/2001/04/xmlenc#sha256".into()),
5103                mgf_algorithm: None,
5104                oaep_params: None,
5105            },
5106            cipher_data: super::super::CipherData::Value {
5107                value: STANDARD.encode(wrapped),
5108            },
5109            reference_list: None,
5110            carried_key_name: None,
5111        };
5112        let resolved = PrivateKeyDecryptor::new(private_key)
5113            .resolve_key(
5114                crate::provider::default_provider(),
5115                DataEncryptionAlgorithm::Aes128Gcm,
5116                Some(&encrypted_key),
5117            )
5118            .expect("legacy OAEP URI with SHA-256 must resolve");
5119        assert_eq!(resolved, session_key);
5120    }
5121
5122    #[test]
5123    fn decrypts_sha384_oaep_with_the_xmlenc_digest_uri() {
5124        // XML Encryption 1.1 reserves xmlenc#sha384 for SHA-384. Exercise both
5125        // OAEP algorithm URIs with their absent/explicit MGF1-SHA1 forms.
5126        let private_key = RsaPrivateKey::from_pkcs8_pem(include_str!(
5127            "../../tests/fixtures/keys/rsa/rsa-2048-key.pem"
5128        ))
5129        .expect("RSA donor private key must parse");
5130        let public_key = RsaPublicKey::from(&private_key);
5131        let session_key = [9_u8; 16];
5132        let digest = "http://www.w3.org/2001/04/xmlenc#sha384";
5133
5134        for (algorithm, mgf_algorithm) in [
5135            ("http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p", None),
5136            (
5137                "http://www.w3.org/2009/xmlenc11#rsa-oaep",
5138                Some("http://www.w3.org/2009/xmlenc11#mgf1sha1"),
5139            ),
5140        ] {
5141            let wrapped = public_key
5142                .encrypt(
5143                    &mut ChaCha20Rng::from_seed([23_u8; 32]),
5144                    Oaep::<Sha384, Sha1>::new_with_mgf_hash(),
5145                    &session_key,
5146                )
5147                .expect("SHA-384 OAEP test wrapping must succeed");
5148            let encrypted_key = EncryptedKey {
5149                id: None,
5150                sources: Default::default(),
5151                recipient: None,
5152                key_name: None,
5153                encryption_method: super::super::EncryptionMethod {
5154                    chacha: None,
5155                    algorithm: algorithm.into(),
5156                    key_size_bits: None,
5157                    oaep_digest: Some(digest.into()),
5158                    mgf_algorithm: mgf_algorithm.map(str::to_owned),
5159                    oaep_params: None,
5160                },
5161                cipher_data: super::super::CipherData::Value {
5162                    value: STANDARD.encode(wrapped),
5163                },
5164                reference_list: None,
5165                carried_key_name: None,
5166            };
5167            let resolved = PrivateKeyDecryptor::new(private_key.clone())
5168                .resolve_key(
5169                    crate::provider::default_provider(),
5170                    DataEncryptionAlgorithm::Aes128Gcm,
5171                    Some(&encrypted_key),
5172                )
5173                .expect("official XMLENC SHA-384 URI must resolve");
5174            assert_eq!(resolved, session_key);
5175        }
5176    }
5177
5178    #[test]
5179    fn rejects_unknown_oaep_digest_and_mgf_as_unsupported() {
5180        // Unknown algorithm URIs are declaration errors, not generic RSA failures.
5181        let private_key = RsaPrivateKey::from_pkcs8_pem(include_str!(
5182            "../../tests/fixtures/keys/rsa/rsa-2048-key.pem"
5183        ))
5184        .expect("RSA donor private key must parse");
5185        let decryptor = PrivateKeyDecryptor::new(private_key);
5186        let mut encrypted_key = EncryptedKey {
5187            sources: Default::default(),
5188            id: None,
5189            recipient: None,
5190            key_name: None,
5191            encryption_method: super::super::EncryptionMethod {
5192                chacha: None,
5193                algorithm: "http://www.w3.org/2009/xmlenc11#rsa-oaep".into(),
5194                key_size_bits: None,
5195                oaep_digest: Some("urn:unsupported:digest".into()),
5196                mgf_algorithm: Some("http://www.w3.org/2009/xmlenc11#mgf1sha1".into()),
5197                oaep_params: None,
5198            },
5199            cipher_data: super::super::CipherData::Value {
5200                value: STANDARD.encode([0_u8; 256]),
5201            },
5202            reference_list: None,
5203            carried_key_name: None,
5204        };
5205        assert!(matches!(
5206            decryptor.resolve_key(crate::provider::default_provider(), DataEncryptionAlgorithm::Aes128Gcm, Some(&encrypted_key)),
5207            Err(XmlEncError::UnsupportedAlgorithm(uri)) if uri == "urn:unsupported:digest"
5208        ));
5209
5210        encrypted_key.encryption_method.oaep_digest = None;
5211        encrypted_key.encryption_method.mgf_algorithm = Some("urn:unsupported:mgf".into());
5212        assert!(matches!(
5213            decryptor.resolve_key(crate::provider::default_provider(), DataEncryptionAlgorithm::Aes128Gcm, Some(&encrypted_key)),
5214            Err(XmlEncError::UnsupportedAlgorithm(uri)) if uri == "urn:unsupported:mgf"
5215        ));
5216    }
5217
5218    #[test]
5219    fn decryption_policy_enforces_oaep_digest_and_plaintext_limits() {
5220        // Algorithm and allocation policies are checked before key resolution
5221        // or plaintext materialization, including the document-declared MGF.
5222        let encrypted_key = EncryptedKey {
5223            id: None,
5224            recipient: Some("selected".into()),
5225            sources: Default::default(),
5226            key_name: None,
5227            encryption_method: super::super::EncryptionMethod {
5228                chacha: None,
5229                algorithm: KeyTransportAlgorithm::RsaOaepMgf1p.uri().into(),
5230                key_size_bits: None,
5231                oaep_digest: Some(OaepDigestAlgorithm::Sha256.uri().into()),
5232                mgf_algorithm: Some(
5233                    OaepDigestAlgorithm::Sha384
5234                        .mgf_uri()
5235                        .expect("MGF1-SHA384 URI")
5236                        .into(),
5237                ),
5238                oaep_params: None,
5239            },
5240            cipher_data: super::super::CipherData::Value {
5241                value: STANDARD.encode([0_u8; 256]),
5242            },
5243            reference_list: None,
5244            carried_key_name: None,
5245        };
5246        let encrypted = EncryptedData {
5247            encapsulation_methods: Vec::new(),
5248            id: None,
5249            derived_keys: Vec::new(),
5250            agreement_methods: Vec::new(),
5251            encrypted_type: None,
5252            key_name: None,
5253            encryption_method: super::super::EncryptionMethod {
5254                chacha: None,
5255                algorithm: DataEncryptionAlgorithm::Aes128Gcm.uri().into(),
5256                key_size_bits: None,
5257                oaep_digest: None,
5258                mgf_algorithm: None,
5259                oaep_params: None,
5260            },
5261            encrypted_keys: vec![encrypted_key],
5262            cipher_data: super::super::CipherData::Value {
5263                value: STANDARD.encode([0_u8; 28]),
5264            },
5265        };
5266        let policy = crate::policy::DecryptionPolicy {
5267            oaep_digests: Some(std::collections::HashSet::from([
5268                OaepDigestAlgorithm::Sha256,
5269            ])),
5270            ..crate::policy::DecryptionPolicy::default()
5271        };
5272        assert!(matches!(
5273            DecryptContext::new(&RecipientKeyResolver {
5274                recipient: "selected",
5275                key: vec![0_u8; 16],
5276            })
5277            .policy(policy)
5278            .decrypt_data(&encrypted),
5279            Err(XmlEncError::Policy(
5280                crate::policy::PolicyViolation::Algorithm { algorithm, .. }
5281            )) if algorithm == OaepDigestAlgorithm::Sha384.mgf_uri().expect("MGF1-SHA384 URI")
5282        ));
5283
5284        let ciphertext = crate::provider::default_provider()
5285            .encrypt_data(DataEncryptionAlgorithm::Aes128Gcm, &[0_u8; 16], b"four")
5286            .expect("test encryption must succeed");
5287        let bounded = EncryptedData {
5288            encrypted_keys: Vec::new(),
5289            cipher_data: super::super::CipherData::Value {
5290                value: STANDARD.encode(ciphertext),
5291            },
5292            ..encrypted
5293        };
5294        let policy = crate::policy::DecryptionPolicy {
5295            resources: crate::policy::ResourcePolicy {
5296                max_encryption_plaintext_bytes: 3,
5297                ..crate::policy::ResourcePolicy::default()
5298            },
5299            ..crate::policy::DecryptionPolicy::default()
5300        };
5301        assert!(matches!(
5302            DecryptContext::new(&SymmetricKeyDecryptor::new([0_u8; 16]))
5303                .policy(policy)
5304                .decrypt_data(&bounded),
5305            Err(XmlEncError::Policy(
5306                crate::policy::PolicyViolation::ResourceLimit {
5307                    resource: crate::policy::resource_name::ENCRYPTION_PLAINTEXT_BYTES,
5308                    maximum: 3,
5309                    actual: 4
5310                }
5311            ))
5312        ));
5313
5314        let cbc_ciphertext = crate::provider::default_provider()
5315            .encrypt_data(DataEncryptionAlgorithm::Aes128Cbc, &[0_u8; 16], b"four")
5316            .expect("test CBC encryption must succeed");
5317        let bounded_cbc = EncryptedData {
5318            encryption_method: super::super::EncryptionMethod {
5319                chacha: None,
5320                algorithm: DataEncryptionAlgorithm::Aes128Cbc.uri().into(),
5321                key_size_bits: None,
5322                oaep_digest: None,
5323                mgf_algorithm: None,
5324                oaep_params: None,
5325            },
5326            encrypted_keys: Vec::new(),
5327            cipher_data: super::super::CipherData::Value {
5328                value: STANDARD.encode(cbc_ciphertext),
5329            },
5330            ..bounded
5331        };
5332        let policy = crate::policy::DecryptionPolicy {
5333            resources: crate::policy::ResourcePolicy {
5334                max_encryption_plaintext_bytes: 4,
5335                ..crate::policy::ResourcePolicy::default()
5336            },
5337            ..crate::policy::DecryptionPolicy::default()
5338        };
5339        assert_eq!(
5340            DecryptContext::new(&SymmetricKeyDecryptor::new([0_u8; 16]))
5341                .policy(policy)
5342                .decrypt_data(&bounded_cbc)
5343                .expect("CBC plaintext at the configured limit must decrypt"),
5344            DecryptedContent::Bytes(b"four".to_vec())
5345        );
5346    }
5347
5348    #[test]
5349    fn typed_decryption_input_cannot_bypass_metadata_policy() {
5350        // Callers may construct EncryptedData directly instead of using the XML
5351        // parser, so the operation boundary must enforce the same metadata cap.
5352        let ciphertext = crate::provider::default_provider()
5353            .encrypt_data(DataEncryptionAlgorithm::Aes128Gcm, &[0_u8; 16], b"data")
5354            .expect("test encryption must succeed");
5355        let encrypted = EncryptedData {
5356            encapsulation_methods: Vec::new(),
5357            id: Some("oversized".into()),
5358            derived_keys: Vec::new(),
5359            agreement_methods: Vec::new(),
5360            encrypted_type: None,
5361            key_name: None,
5362            encryption_method: super::super::EncryptionMethod {
5363                chacha: None,
5364                algorithm: DataEncryptionAlgorithm::Aes128Gcm.uri().into(),
5365                key_size_bits: None,
5366                oaep_digest: None,
5367                mgf_algorithm: None,
5368                oaep_params: None,
5369            },
5370            encrypted_keys: Vec::new(),
5371            cipher_data: super::super::CipherData::Value {
5372                value: STANDARD.encode(ciphertext),
5373            },
5374        };
5375        let policy = crate::policy::DecryptionPolicy {
5376            resources: crate::policy::ResourcePolicy {
5377                max_encryption_metadata_bytes: 8,
5378                ..crate::policy::ResourcePolicy::default()
5379            },
5380            ..crate::policy::DecryptionPolicy::default()
5381        };
5382
5383        let resolver = AllCallsResolver {
5384            calls: Cell::new(0),
5385            key: vec![0_u8; 16],
5386        };
5387        assert!(matches!(
5388            DecryptContext::new(&resolver)
5389                .policy(policy)
5390                .decrypt_data(&encrypted),
5391            Err(XmlEncError::Policy(
5392                crate::policy::PolicyViolation::ResourceLimit {
5393                    resource: crate::policy::resource_name::ENCRYPTION_METADATA_BYTES,
5394                    maximum: 8,
5395                    actual: 9,
5396                }
5397            ))
5398        ));
5399        assert_eq!(
5400            resolver.calls.get(),
5401            0,
5402            "metadata preflight must gate resolver-controlled work"
5403        );
5404    }
5405
5406    #[test]
5407    fn typed_cipher_values_are_bounded_before_decode_or_resolution() {
5408        // Public typed input bypasses the XML parser, so the decryption boundary
5409        // must re-establish both content and recipient CipherValue size invariants.
5410        let key = [0x41_u8; 16];
5411        let ciphertext = crate::provider::default_provider()
5412            .encrypt_data(DataEncryptionAlgorithm::Aes128Gcm, &key, b"data")
5413            .expect("test encryption must succeed");
5414        let mut encrypted = EncryptedData {
5415            encapsulation_methods: Vec::new(),
5416            id: None,
5417            derived_keys: Vec::new(),
5418            agreement_methods: Vec::new(),
5419            encrypted_type: None,
5420            key_name: None,
5421            encryption_method: super::super::EncryptionMethod {
5422                chacha: None,
5423                algorithm: DataEncryptionAlgorithm::Aes128Gcm.uri().into(),
5424                key_size_bits: None,
5425                oaep_digest: None,
5426                mgf_algorithm: None,
5427                oaep_params: None,
5428            },
5429            encrypted_keys: Vec::new(),
5430            cipher_data: super::super::CipherData::Value {
5431                value: STANDARD.encode(ciphertext),
5432            },
5433        };
5434        let policy = crate::policy::DecryptionPolicy {
5435            resources: crate::policy::ResourcePolicy {
5436                max_encryption_plaintext_bytes: 4,
5437                ..crate::policy::ResourcePolicy::default()
5438            },
5439            ..crate::policy::DecryptionPolicy::default()
5440        };
5441        *encrypted
5442            .cipher_data
5443            .inline_value_mut()
5444            .expect("inline ciphertext") = "A".repeat(48);
5445        assert!(matches!(
5446            DecryptContext::new(&SymmetricKeyDecryptor::new(key))
5447                .policy(policy)
5448                .decrypt_data(&encrypted),
5449            Err(XmlEncError::Policy(
5450                crate::policy::PolicyViolation::ResourceLimit {
5451                    resource: crate::policy::resource_name::ENCRYPTION_PLAINTEXT_BYTES,
5452                    ..
5453                }
5454            ))
5455        ));
5456
5457        *encrypted
5458            .cipher_data
5459            .inline_value_mut()
5460            .expect("inline ciphertext") = STANDARD.encode([0_u8; 28]);
5461        encrypted.encrypted_keys.push(EncryptedKey {
5462            sources: Default::default(),
5463            id: None,
5464            recipient: None,
5465            key_name: None,
5466            encryption_method: super::super::EncryptionMethod {
5467                chacha: None,
5468                algorithm: KeyWrapAlgorithm::AesKw128.uri().into(),
5469                key_size_bits: None,
5470                oaep_digest: None,
5471                mgf_algorithm: None,
5472                oaep_params: None,
5473            },
5474            cipher_data: super::super::CipherData::Value {
5475                value: "A".repeat(MAX_CIPHER_VALUE_BASE64_LEN + 4),
5476            },
5477            reference_list: None,
5478            carried_key_name: None,
5479        });
5480        let resolver = CountingResolver {
5481            candidate_calls: Cell::new(0),
5482            key: key.to_vec(),
5483        };
5484        assert!(matches!(
5485            DecryptContext::new(&resolver).decrypt_data(&encrypted),
5486            Err(XmlEncError::InvalidStructure(_))
5487        ));
5488        assert_eq!(resolver.candidate_calls.get(), 0);
5489
5490        *encrypted.encrypted_keys[0]
5491            .cipher_data
5492            .inline_value_mut()
5493            .expect("inline wrapped key") = "AAAA".into();
5494        let aggregate_encoded_len = encrypted
5495            .cipher_data
5496            .inline_value()
5497            .expect("inline ciphertext")
5498            .len()
5499            + encrypted.encrypted_keys[0]
5500                .cipher_data
5501                .inline_value()
5502                .expect("inline wrapped key")
5503                .len();
5504        let policy = crate::policy::DecryptionPolicy {
5505            resources: crate::policy::ResourcePolicy {
5506                max_encryption_plaintext_bytes: 4,
5507                max_xml_document_bytes: aggregate_encoded_len - 1,
5508                ..crate::policy::ResourcePolicy::default()
5509            },
5510            ..crate::policy::DecryptionPolicy::default()
5511        };
5512        let resolver = CountingResolver {
5513            candidate_calls: Cell::new(0),
5514            key: key.to_vec(),
5515        };
5516        assert!(matches!(
5517            DecryptContext::new(&resolver)
5518                .policy(policy)
5519                .decrypt_data(&encrypted),
5520            Err(XmlEncError::Policy(
5521                crate::policy::PolicyViolation::ResourceLimit {
5522                    resource:
5523                        crate::policy::resource_name::AGGREGATE_ENCRYPTION_CIPHER_VALUE_BYTES,
5524                    maximum,
5525                    actual,
5526                }
5527            )) if maximum == aggregate_encoded_len - 1 && actual == aggregate_encoded_len
5528        ));
5529        assert_eq!(resolver.candidate_calls.get(), 0);
5530    }
5531
5532    #[cfg(feature = "experimental-pq")]
5533    #[test]
5534    fn typed_kem_width_is_checked_before_any_resolver_dispatch() {
5535        // Exercise the existing facade preflight with a permissive custom
5536        // resolver, including nested KEKs, rather than built-in KEM recovery.
5537        let document = crate::XmlDomDocument::parse(include_str!(
5538            "../../tests/fixtures/xmlenc/aleksey-xmlenc-01/enc-aes128gcm-em-ml-kem-512.xml"
5539        ))
5540        .expect("valid donor XML");
5541        let node = document
5542            .descendants()
5543            .find(|node| node.has_tag_name((crate::xmlenc::types::XMLENC_NS, "EncryptedData")))
5544            .expect("donor EncryptedData");
5545        let mut policy = crate::policy::DecryptionPolicy::default();
5546        policy
5547            .key_establishment
5548            .encapsulation_algorithms
5549            .insert(crate::provider::KeyEncapsulationAlgorithm::MlKem512);
5550        let data = super::super::parse_encrypted_data_node_with_policy(node, &policy)
5551            .expect("valid donor encryption metadata");
5552        for width in [0, 767, 769] {
5553            for nested in [false, true] {
5554                let mut encrypted = data.clone();
5555                encrypted.encapsulation_methods[0]
5556                    .ciphertext
5557                    .resize(width, 0);
5558                if nested {
5559                    encrypted.encrypted_keys.push(EncryptedKey {
5560                        sources: super::super::EncryptionKeySources {
5561                            encapsulation_methods: std::mem::take(
5562                                &mut encrypted.encapsulation_methods,
5563                            ),
5564                            ..Default::default()
5565                        },
5566                        id: None,
5567                        recipient: None,
5568                        key_name: None,
5569                        encryption_method: super::super::EncryptionMethod {
5570                            chacha: None,
5571                            algorithm: KeyWrapAlgorithm::AesKw128.uri().into(),
5572                            key_size_bits: None,
5573                            oaep_digest: None,
5574                            mgf_algorithm: None,
5575                            oaep_params: None,
5576                        },
5577                        cipher_data: super::super::CipherData::Bytes(vec![0; 24]),
5578                        reference_list: None,
5579                        carried_key_name: None,
5580                    });
5581                }
5582                let resolver = CountingResolver {
5583                    candidate_calls: Cell::new(0),
5584                    key: vec![0; 16],
5585                };
5586                let error = DecryptContext::new(&resolver)
5587                    .policy(policy.clone())
5588                    .decrypt_data(&encrypted)
5589                    .expect_err("malformed typed KEM width rejected");
5590                assert!(
5591                    error
5592                        .to_string()
5593                        .contains("ciphertext size does not match Algorithm"),
5594                    "{error}"
5595                );
5596                assert_eq!(resolver.candidate_calls.get(), 0);
5597            }
5598        }
5599        // A custom resolver cannot bypass the facade's default KEM/CBC gate.
5600        let mut encrypted = data;
5601        encrypted.encryption_method.algorithm = DataEncryptionAlgorithm::Aes256Cbc.uri().into();
5602        encrypted.cipher_data = super::super::CipherData::Bytes(vec![0; 32]);
5603        let resolver = CountingResolver {
5604            candidate_calls: Cell::new(0),
5605            key: vec![0; 32],
5606        };
5607        assert!(matches!(
5608            DecryptContext::new(&resolver)
5609                .policy(policy)
5610                .decrypt_data(&encrypted),
5611            Err(XmlEncError::Policy(
5612                crate::policy::PolicyViolation::Algorithm {
5613                    operation: "direct KEM content without external authentication",
5614                    ..
5615                }
5616            ))
5617        ));
5618        assert_eq!(resolver.candidate_calls.get(), 0);
5619    }
5620
5621    #[cfg(feature = "experimental-pq")]
5622    #[test]
5623    fn typed_kem_ciphertext_shares_aggregate_cipher_value_budget() {
5624        // Typed input must charge the same base64-projected KEM ciphertext as
5625        // XML input, including descriptors nested under an EncryptedKey.
5626        let xml = include_str!(
5627            "../../tests/fixtures/xmlenc/aleksey-xmlenc-01/enc-aes128gcm-em-ml-kem-512.xml"
5628        );
5629        let document = crate::XmlDomDocument::parse(xml).expect("donor XML parses");
5630        let node = document
5631            .descendants()
5632            .find(|node| node.has_tag_name((crate::xmlenc::types::XMLENC_NS, "EncryptedData")))
5633            .expect("donor EncryptedData");
5634        let mut policy = crate::policy::DecryptionPolicy::default();
5635        policy
5636            .key_establishment
5637            .encapsulation_algorithms
5638            .insert(crate::provider::KeyEncapsulationAlgorithm::MlKem512);
5639        let mut encrypted = super::super::parse_encrypted_data_node_with_policy(node, &policy)
5640            .expect("permitted donor recipient");
5641        let content = cipher_data_storage_len(&encrypted.cipher_data);
5642        let kem_encoded = 4 * encrypted.encapsulation_methods[0]
5643            .ciphertext
5644            .len()
5645            .div_ceil(3);
5646        assert!(
5647            validate_typed_cipher_values(
5648                &encrypted,
5649                DataEncryptionAlgorithm::Aes128Gcm,
5650                usize::MAX / 2,
5651                content + kem_encoded
5652            )
5653            .is_ok()
5654        );
5655        for nested in [false, true] {
5656            if nested {
5657                encrypted.encrypted_keys.push(EncryptedKey {
5658                    sources: super::super::EncryptionKeySources {
5659                        encapsulation_methods: std::mem::take(&mut encrypted.encapsulation_methods),
5660                        ..Default::default()
5661                    },
5662                    id: None,
5663                    recipient: None,
5664                    key_name: None,
5665                    encryption_method: super::super::EncryptionMethod {
5666                        chacha: None,
5667                        algorithm: KeyWrapAlgorithm::AesKw128.uri().into(),
5668                        key_size_bits: None,
5669                        oaep_digest: None,
5670                        mgf_algorithm: None,
5671                        oaep_params: None,
5672                    },
5673                    cipher_data: super::super::CipherData::Bytes(vec![0; 24]),
5674                    reference_list: None,
5675                    carried_key_name: None,
5676                });
5677            }
5678            let total = content + kem_encoded + if nested { 24 } else { 0 };
5679            policy.resources.max_xml_document_bytes = total - 1;
5680            let resolver = CountingResolver {
5681                candidate_calls: Cell::new(0),
5682                key: vec![0; 16],
5683            };
5684            assert!(
5685                matches!(DecryptContext::new(&resolver).policy(policy.clone()).decrypt_data(&encrypted), Err(XmlEncError::Policy(crate::policy::PolicyViolation::ResourceLimit { resource: crate::policy::resource_name::AGGREGATE_ENCRYPTION_CIPHER_VALUE_BYTES, maximum, actual })) if maximum == total - 1 && actual == total)
5686            );
5687            assert_eq!(resolver.candidate_calls.get(), 0);
5688        }
5689    }
5690
5691    #[test]
5692    fn typed_legacy_oaep_accepts_explicit_mgf() {
5693        // The legacy URI defaults to MGF1-SHA1 when MGF is absent, but
5694        // libxmlsec1 also accepts the XMLEnc 1.1 child explicitly.
5695        let key = [0x43_u8; 16];
5696        let ciphertext = crate::provider::default_provider()
5697            .encrypt_data(DataEncryptionAlgorithm::Aes128Gcm, &key, b"data")
5698            .expect("test encryption must succeed");
5699        let encrypted = EncryptedData {
5700            encapsulation_methods: Vec::new(),
5701            id: None,
5702            derived_keys: Vec::new(),
5703            agreement_methods: Vec::new(),
5704            encrypted_type: None,
5705            key_name: None,
5706            encryption_method: super::super::EncryptionMethod {
5707                chacha: None,
5708                algorithm: DataEncryptionAlgorithm::Aes128Gcm.uri().into(),
5709                key_size_bits: None,
5710                oaep_digest: None,
5711                mgf_algorithm: None,
5712                oaep_params: None,
5713            },
5714            encrypted_keys: vec![EncryptedKey {
5715                sources: Default::default(),
5716                id: None,
5717                recipient: None,
5718                key_name: None,
5719                encryption_method: super::super::EncryptionMethod {
5720                    chacha: None,
5721                    algorithm: KeyTransportAlgorithm::RsaOaepMgf1p.uri().into(),
5722                    key_size_bits: None,
5723                    oaep_digest: Some(OaepDigestAlgorithm::Sha256.uri().into()),
5724                    mgf_algorithm: Some(
5725                        OaepDigestAlgorithm::Sha384
5726                            .mgf_uri()
5727                            .expect("MGF1-SHA384 URI")
5728                            .into(),
5729                    ),
5730                    oaep_params: None,
5731                },
5732                cipher_data: super::super::CipherData::Value {
5733                    value: STANDARD.encode([0_u8; 256]),
5734                },
5735                reference_list: None,
5736                carried_key_name: None,
5737            }],
5738            cipher_data: super::super::CipherData::Value {
5739                value: STANDARD.encode(ciphertext),
5740            },
5741        };
5742        let resolver = CountingResolver {
5743            candidate_calls: Cell::new(0),
5744            key: key.to_vec(),
5745        };
5746
5747        assert_eq!(
5748            DecryptContext::new(&resolver)
5749                .decrypt_data(&encrypted)
5750                .expect("explicit legacy OAEP MGF must reach key resolution"),
5751            DecryptedContent::Bytes(b"data".to_vec())
5752        );
5753        assert_eq!(resolver.candidate_calls.get(), 1);
5754    }
5755
5756    #[test]
5757    fn typed_zero_key_size_is_rejected_before_key_resolution() {
5758        // Parsed KeySize values are positive. Caller-constructed values must
5759        // preserve the same invariant for algorithms without a fixed AES width.
5760        let key = [0x45_u8; 16];
5761        let ciphertext = crate::provider::default_provider()
5762            .encrypt_data(DataEncryptionAlgorithm::Aes128Gcm, &key, b"data")
5763            .expect("test encryption must succeed");
5764        let encrypted = EncryptedData {
5765            encapsulation_methods: Vec::new(),
5766            id: None,
5767            derived_keys: Vec::new(),
5768            agreement_methods: Vec::new(),
5769            encrypted_type: None,
5770            key_name: None,
5771            encryption_method: super::super::EncryptionMethod {
5772                chacha: None,
5773                algorithm: DataEncryptionAlgorithm::Aes128Gcm.uri().into(),
5774                key_size_bits: None,
5775                oaep_digest: None,
5776                mgf_algorithm: None,
5777                oaep_params: None,
5778            },
5779            encrypted_keys: vec![EncryptedKey {
5780                id: None,
5781                sources: Default::default(),
5782                recipient: None,
5783                key_name: None,
5784                encryption_method: super::super::EncryptionMethod {
5785                    chacha: None,
5786                    algorithm: KeyTransportAlgorithm::RsaOaep11.uri().into(),
5787                    key_size_bits: Some(0),
5788                    oaep_digest: Some(OaepDigestAlgorithm::Sha256.uri().into()),
5789                    mgf_algorithm: Some(
5790                        OaepDigestAlgorithm::Sha256
5791                            .mgf_uri()
5792                            .expect("MGF1-SHA256 URI")
5793                            .into(),
5794                    ),
5795                    oaep_params: None,
5796                },
5797                cipher_data: super::super::CipherData::Value {
5798                    value: STANDARD.encode([0_u8; 256]),
5799                },
5800                reference_list: None,
5801                carried_key_name: None,
5802            }],
5803            cipher_data: super::super::CipherData::Value {
5804                value: STANDARD.encode(ciphertext),
5805            },
5806        };
5807        let resolver = CountingResolver {
5808            candidate_calls: Cell::new(0),
5809            key: key.to_vec(),
5810        };
5811
5812        assert!(matches!(
5813            DecryptContext::new(&resolver).decrypt_data(&encrypted),
5814            Err(XmlEncError::InvalidStructure(message))
5815                if message == "KeySize must be a positive integer"
5816        ));
5817        assert_eq!(resolver.candidate_calls.get(), 0);
5818    }
5819
5820    #[test]
5821    fn typed_content_method_is_validated_before_key_resolution() {
5822        // Caller-constructed values bypass XML parsing, so a fixed-size AES
5823        // KeySize mismatch must fail at the operation boundary.
5824        let key = [0x44_u8; 16];
5825        let ciphertext = crate::provider::default_provider()
5826            .encrypt_data(DataEncryptionAlgorithm::Aes128Gcm, &key, b"data")
5827            .expect("test encryption must succeed");
5828        let encrypted = EncryptedData {
5829            encapsulation_methods: Vec::new(),
5830            id: None,
5831            derived_keys: Vec::new(),
5832            agreement_methods: Vec::new(),
5833            encrypted_type: None,
5834            key_name: None,
5835            encryption_method: super::super::EncryptionMethod {
5836                chacha: None,
5837                algorithm: DataEncryptionAlgorithm::Aes128Gcm.uri().into(),
5838                key_size_bits: Some(256),
5839                oaep_digest: None,
5840                mgf_algorithm: None,
5841                oaep_params: None,
5842            },
5843            encrypted_keys: Vec::new(),
5844            cipher_data: super::super::CipherData::Value {
5845                value: STANDARD.encode(ciphertext),
5846            },
5847        };
5848        let resolver = AllCallsResolver {
5849            calls: Cell::new(0),
5850            key: key.to_vec(),
5851        };
5852
5853        assert!(matches!(
5854            DecryptContext::new(&resolver).decrypt_data(&encrypted),
5855            Err(XmlEncError::InvalidStructure(message))
5856                if message.contains("requires KeySize 128, got 256")
5857        ));
5858        assert_eq!(resolver.calls.get(), 0);
5859    }
5860
5861    #[test]
5862    fn unknown_encrypted_key_algorithm_never_reaches_resolver() {
5863        // Extension URIs cannot bypass transport/wrap allowlists by relying on
5864        // an application resolver that happens to return usable key bytes.
5865        let key = [0x42_u8; 16];
5866        let ciphertext = crate::provider::default_provider()
5867            .encrypt_data(DataEncryptionAlgorithm::Aes128Gcm, &key, b"data")
5868            .expect("test encryption must succeed");
5869        let encrypted = EncryptedData {
5870            encapsulation_methods: Vec::new(),
5871            id: None,
5872            derived_keys: Vec::new(),
5873            agreement_methods: Vec::new(),
5874            encrypted_type: None,
5875            key_name: None,
5876            encryption_method: super::super::EncryptionMethod {
5877                chacha: None,
5878                algorithm: DataEncryptionAlgorithm::Aes128Gcm.uri().into(),
5879                key_size_bits: None,
5880                oaep_digest: None,
5881                mgf_algorithm: None,
5882                oaep_params: None,
5883            },
5884            encrypted_keys: vec![EncryptedKey {
5885                id: None,
5886                recipient: None,
5887                sources: Default::default(),
5888                key_name: None,
5889                encryption_method: super::super::EncryptionMethod {
5890                    chacha: None,
5891                    algorithm: "urn:example:unknown-key-algorithm".into(),
5892                    key_size_bits: None,
5893                    oaep_digest: None,
5894                    mgf_algorithm: None,
5895                    oaep_params: None,
5896                },
5897                cipher_data: super::super::CipherData::Value {
5898                    value: STANDARD.encode([0_u8; 24]),
5899                },
5900                reference_list: None,
5901                carried_key_name: None,
5902            }],
5903            cipher_data: super::super::CipherData::Value {
5904                value: STANDARD.encode(ciphertext),
5905            },
5906        };
5907        let resolver = CountingResolver {
5908            candidate_calls: Cell::new(0),
5909            key: key.to_vec(),
5910        };
5911
5912        assert!(matches!(
5913            DecryptContext::new(&resolver).decrypt_data(&encrypted),
5914            Err(XmlEncError::UnsupportedAlgorithm(_))
5915        ));
5916        assert_eq!(resolver.candidate_calls.get(), 0);
5917    }
5918
5919    #[test]
5920    fn cbc_padding_errors_do_not_expose_decrypted_octets() {
5921        // The error contract hides padding details, but callers still need an
5922        // authenticated envelope or a policy that rejects unauthenticated CBC.
5923        let error = map_data_decryption_error(
5924            DataEncryptionAlgorithm::Aes128Cbc,
5925            32,
5926            crate::provider::ProviderError::InvalidInput(
5927                crate::provider::ProviderInputError::AesCbcCiphertext,
5928            ),
5929        );
5930
5931        assert_eq!(error.to_string(), "invalid XMLEnc padding");
5932    }
5933
5934    #[test]
5935    fn replaces_element_and_content_in_caller_owned_documents() {
5936        // Element plaintext replaces the encrypted node itself, while Content
5937        // plaintext becomes children of the existing parent element.
5938        let key = [0x31_u8; 16];
5939        let element = encrypted_gcm_element(
5940            "http://www.w3.org/2001/04/xmlenc#Element",
5941            "<secret id=\"visible\">value</secret>",
5942            None,
5943            true,
5944            &key,
5945        );
5946        assert_eq!(
5947            decrypt_document(&element, None, &SymmetricKeyDecryptor::new(key))
5948                .expect("root Element replacement must succeed"),
5949            "<secret id=\"visible\">value</secret>"
5950        );
5951
5952        let content = encrypted_gcm_element(
5953            "http://www.w3.org/2001/04/xmlenc#Content",
5954            "before<child/>after",
5955            None,
5956            false,
5957            &key,
5958        );
5959        let document =
5960            format!("<root xmlns:xenc=\"{XMLENC_NS}\"><prefix/>{content}<suffix/></root>");
5961        let replaced = decrypt_document(&document, None, &SymmetricKeyDecryptor::new(key))
5962            .expect("nested Content replacement must succeed");
5963        assert_eq!(
5964            replaced,
5965            format!(
5966                "<root xmlns:xenc=\"{XMLENC_NS}\"><prefix/>before<child/>after<suffix/></root>"
5967            )
5968        );
5969    }
5970
5971    #[test]
5972    fn accepts_whitespace_and_comments_around_element_plaintext() {
5973        // Element serialization may carry harmless boundary whitespace/comments;
5974        // they must be preserved while the fragment still contains one element.
5975        let key = [0x34_u8; 16];
5976        let plaintext = "\n<!--before--><secret/><!--after-->\n";
5977        let encrypted = encrypted_gcm_element(
5978            "http://www.w3.org/2001/04/xmlenc#Element",
5979            plaintext,
5980            None,
5981            true,
5982            &key,
5983        );
5984
5985        assert_eq!(
5986            decrypt_document(&encrypted, None, &SymmetricKeyDecryptor::new(key))
5987                .expect("one element with boundary trivia must be accepted"),
5988            plaintext
5989        );
5990    }
5991
5992    #[test]
5993    fn decrypts_unknown_and_empty_type_hints_as_opaque_bytes() {
5994        // Type is an application hint, not an algorithm constraint. Unknown and
5995        // empty values must not prevent decryption of otherwise valid binary data.
5996        let key = [0x35_u8; 16];
5997        let plaintext = "\0opaque\u{ff}bytes";
5998        let unknown = encrypted_gcm_element("urn:example:binary", plaintext, None, true, &key);
5999        let empty = encrypted_gcm_element("", plaintext, None, true, &key).replacen(
6000            "<xenc:EncryptedData",
6001            "<xenc:EncryptedData Type=\"\"",
6002            1,
6003        );
6004
6005        let parsed = parse_encrypted_data(&unknown).expect("unknown Type must remain parseable");
6006        assert_eq!(
6007            parsed.encrypted_type,
6008            Some(EncryptedDataType::Other("urn:example:binary".into()))
6009        );
6010        assert!(matches!(
6011            decrypt_document(&unknown, None, &SymmetricKeyDecryptor::new(key)),
6012            Err(XmlEncError::ReplacementRequiresXml)
6013        ));
6014
6015        for encrypted in [unknown, empty] {
6016            assert_eq!(
6017                decrypt(&encrypted, &SymmetricKeyDecryptor::new(key))
6018                    .expect("opaque Type hints must not block decryption"),
6019                DecryptedContent::Bytes(plaintext.as_bytes().to_vec())
6020            );
6021        }
6022    }
6023
6024    #[test]
6025    fn selects_document_encrypted_data_by_id_and_rejects_ambiguity() {
6026        // Selection must never decrypt an arbitrary first match when a document
6027        // contains multiple encrypted regions.
6028        let key = [0x32_u8; 16];
6029        let first = encrypted_gcm_element(
6030            "http://www.w3.org/2001/04/xmlenc#Content",
6031            "first",
6032            Some("first"),
6033            false,
6034            &key,
6035        );
6036        let second = encrypted_gcm_element(
6037            "http://www.w3.org/2001/04/xmlenc#Content",
6038            "second",
6039            Some("second"),
6040            false,
6041            &key,
6042        );
6043        let document = format!("<root xmlns:xenc=\"{XMLENC_NS}\">{first}{second}</root>");
6044        let resolver = SymmetricKeyDecryptor::new(key);
6045        assert!(matches!(
6046            decrypt_document(&document, None, &resolver),
6047            Err(XmlEncError::AmbiguousEncryptedData)
6048        ));
6049        let replaced = decrypt_document(&document, Some("second"), &resolver)
6050            .expect("Id selection must choose exactly one encrypted region");
6051        assert!(replaced.contains("second"));
6052        assert!(replaced.contains("Id=\"first\""));
6053        assert!(matches!(
6054            decrypt_document(&document, Some("missing"), &resolver),
6055            Err(XmlEncError::EncryptedDataNotFound)
6056        ));
6057    }
6058
6059    #[test]
6060    fn selects_encrypted_data_below_a_unique_operation_start_node() {
6061        // CLI-compatible selection starts at an arbitrary ID-bearing ancestor;
6062        // missing/duplicate IDs and multiple encrypted descendants fail closed.
6063        let key = [0x42_u8; 16];
6064        let first = encrypted_gcm_element(
6065            "http://www.w3.org/2001/04/xmlenc#Content",
6066            "first",
6067            None,
6068            false,
6069            &key,
6070        );
6071        let second = encrypted_gcm_element(
6072            "http://www.w3.org/2001/04/xmlenc#Content",
6073            "second",
6074            None,
6075            false,
6076            &key,
6077        );
6078        let document = format!(
6079            "<root xmlns:xenc=\"{XMLENC_NS}\"><scope Id=\"first\">{first}</scope><scope Id=\"second\">{second}</scope></root>"
6080        );
6081        let resolver = SymmetricKeyDecryptor::new(key);
6082        let context = DecryptContext::new(&resolver);
6083        let replaced = context
6084            .decrypt_document_from_start_node(&document, Some("second"))
6085            .expect("ancestor ID must select its encrypted descendant");
6086        assert!(replaced.contains("<scope Id=\"second\">second</scope>"));
6087        assert!(replaced.contains("<scope Id=\"first\"><xenc:EncryptedData"));
6088
6089        assert!(matches!(
6090            context.decrypt_document_from_start_node(&document, Some("missing")),
6091            Err(XmlEncError::SelectedNodeUnavailable { id }) if id == "missing"
6092        ));
6093        let duplicate = document.replace("Id=\"second\"", "Id=\"first\"");
6094        assert!(matches!(
6095            context.decrypt_document_from_start_node(&duplicate, Some("first")),
6096            Err(XmlEncError::SelectedNodeUnavailable { id }) if id == "first"
6097        ));
6098        let ambiguous = format!(
6099            "<root xmlns:xenc=\"{XMLENC_NS}\"><scope Id=\"selected\">{first}{second}</scope></root>"
6100        );
6101        assert!(matches!(
6102            context.decrypt_document_from_start_node(&ambiguous, Some("selected")),
6103            Err(XmlEncError::AmbiguousEncryptedData)
6104        ));
6105
6106        let first_replaced = context
6107            .decrypt_first_document_from_start_node(&ambiguous, Some("selected"))
6108            .expect("first-match selection must leave later encrypted descendants untouched");
6109        assert!(first_replaced.contains("<scope Id=\"selected\">first<xenc:EncryptedData"));
6110        let replaced_document =
6111            Document::parse(&first_replaced).expect("first-match output must remain valid XML");
6112        assert_eq!(
6113            replaced_document
6114                .descendants()
6115                .filter(|node| node.has_tag_name((XMLENC_NS, "EncryptedData")))
6116                .count(),
6117            1
6118        );
6119    }
6120
6121    #[test]
6122    fn rejects_non_xml_or_malformed_document_replacement_plaintext() {
6123        // The document API must not expose binary content or return a document
6124        // made malformed by unauthenticated structure assumptions.
6125        let key = [0x33_u8; 16];
6126        let binary = encrypted_gcm_element("", "binary", None, true, &key);
6127        assert!(matches!(
6128            decrypt_document(&binary, None, &SymmetricKeyDecryptor::new(key)),
6129            Err(XmlEncError::ReplacementRequiresXml)
6130        ));
6131
6132        let malformed = encrypted_gcm_element(
6133            "http://www.w3.org/2001/04/xmlenc#Element",
6134            "<unclosed>",
6135            None,
6136            true,
6137            &key,
6138        );
6139        assert!(matches!(
6140            decrypt_document(&malformed, None, &SymmetricKeyDecryptor::new(key)),
6141            Err(XmlEncError::XmlParse(_))
6142        ));
6143
6144        for invalid_element in ["text-only", "<first/><second/>"] {
6145            let encrypted = encrypted_gcm_element(
6146                "http://www.w3.org/2001/04/xmlenc#Element",
6147                invalid_element,
6148                None,
6149                false,
6150                &key,
6151            );
6152            let document = format!("<root xmlns:xenc=\"{XMLENC_NS}\">{encrypted}</root>");
6153            assert!(
6154                decrypt_document(&document, None, &SymmetricKeyDecryptor::new(key)).is_err(),
6155                "Element plaintext must contain exactly one element: {invalid_element}"
6156            );
6157        }
6158
6159        let content = encrypted_gcm_element(
6160            "http://www.w3.org/2001/04/xmlenc#Content",
6161            "plaintext",
6162            None,
6163            false,
6164            &key,
6165        );
6166        let with_dtd = format!(
6167            "<!DOCTYPE root [<!ATTLIST root Id ID #IMPLIED>]><root xmlns:xenc=\"{XMLENC_NS}\">{content}</root>"
6168        );
6169        assert!(matches!(
6170            decrypt_document(&with_dtd, None, &SymmetricKeyDecryptor::new(key)),
6171            Err(XmlEncError::XmlParse(
6172                crate::xml::dom::ParseError::DtdDetected
6173            ))
6174        ));
6175        let mut policy = crate::policy::DecryptionPolicy::default();
6176        policy.xml.allow_internal_dtd = true;
6177        assert!(
6178            DecryptContext::new(&SymmetricKeyDecryptor::new(key))
6179                .policy(policy)
6180                .decrypt_document(&with_dtd, None)
6181                .expect("explicit internal-DTD opt-in must decrypt")
6182                .contains("plaintext")
6183        );
6184    }
6185
6186    #[test]
6187    fn rejects_plaintext_markup_that_crosses_the_encrypted_region() {
6188        // Parsing only after raw splicing is insufficient: balanced close/reopen
6189        // tags can keep the document valid while moving attacker nodes outside the
6190        // element whose encrypted child is being replaced.
6191        let key = [0x36_u8; 16];
6192        let crossing_markup = "</parent><attacker/><parent>";
6193        for type_uri in [
6194            "http://www.w3.org/2001/04/xmlenc#Content",
6195            "http://www.w3.org/2001/04/xmlenc#Element",
6196        ] {
6197            let encrypted = encrypted_gcm_element(type_uri, crossing_markup, None, false, &key);
6198            let document =
6199                format!("<outer xmlns:xenc=\"{XMLENC_NS}\"><parent>{encrypted}</parent></outer>");
6200            assert!(
6201                decrypt_document(&document, None, &SymmetricKeyDecryptor::new(key)).is_err(),
6202                "{type_uri} plaintext must not escape its replacement boundary"
6203            );
6204        }
6205    }
6206
6207    #[test]
6208    fn document_decryption_applies_byte_and_node_policy_before_parsing() {
6209        // Caller-owned XML must meet the compiled resource policy before the
6210        // initial DOM allocation; reparsed output uses the same node ceiling.
6211        let key = [0x38_u8; 16];
6212        let encrypted = encrypted_gcm_element(
6213            "http://www.w3.org/2001/04/xmlenc#Content",
6214            "plaintext",
6215            None,
6216            false,
6217            &key,
6218        );
6219        let document = format!("<root xmlns:xenc=\"{XMLENC_NS}\"><a/>{encrypted}</root>");
6220        let byte_policy = crate::policy::DecryptionPolicy {
6221            resources: crate::policy::ResourcePolicy {
6222                max_xml_document_bytes: document.len() - 1,
6223                ..crate::policy::ResourcePolicy::default()
6224            },
6225            ..crate::policy::DecryptionPolicy::default()
6226        };
6227        assert!(matches!(
6228            DecryptContext::new(&SymmetricKeyDecryptor::new(key))
6229                .policy(byte_policy)
6230                .decrypt_document(&document, None),
6231            Err(XmlEncError::Policy(crate::policy::PolicyViolation::ResourceLimit {
6232                resource: crate::policy::resource_name::XML_DOCUMENT,
6233                maximum,
6234                actual,
6235            })) if maximum == document.len() - 1 && actual == document.len()
6236        ));
6237
6238        let node_policy = crate::policy::DecryptionPolicy {
6239            resources: crate::policy::ResourcePolicy {
6240                max_xml_nodes: 3,
6241                ..crate::policy::ResourcePolicy::default()
6242            },
6243            ..crate::policy::DecryptionPolicy::default()
6244        };
6245        assert!(matches!(
6246            DecryptContext::new(&SymmetricKeyDecryptor::new(key))
6247                .policy(node_policy)
6248                .decrypt_document(&document, None),
6249            Err(XmlEncError::Policy(
6250                crate::policy::PolicyViolation::ResourceLimit {
6251                    resource: crate::policy::resource_name::XML_NODES,
6252                    maximum: 3,
6253                    actual: 4,
6254                }
6255            ))
6256        ));
6257    }
6258
6259    #[test]
6260    fn decryption_entry_points_enforce_policy_depth() {
6261        // Depth validation precedes EncryptedData selection for both borrowed
6262        // and retained documents, including inputs parsed under wider defaults.
6263        let xml = "<root><child><leaf/></child></root>";
6264        let policy = crate::policy::DecryptionPolicy {
6265            resources: crate::policy::ResourcePolicy {
6266                max_xml_depth: 2,
6267                ..crate::policy::ResourcePolicy::default()
6268            },
6269            ..crate::policy::DecryptionPolicy::default()
6270        };
6271        let resolver = SymmetricKeyDecryptor::new([0_u8; 16]);
6272        let mut document = XmlDocument::parse(xml).expect("wide retained fixture must parse");
6273
6274        assert!(matches!(
6275            DecryptContext::new(&resolver)
6276                .policy(policy.clone())
6277                .decrypt_document(xml, None),
6278            Err(XmlEncError::Policy(
6279                crate::policy::PolicyViolation::ResourceLimit {
6280                    resource: crate::policy::resource_name::XML_DEPTH,
6281                    maximum: 2,
6282                    actual: 3,
6283                }
6284            ))
6285        ));
6286        assert!(matches!(
6287            DecryptContext::new(&resolver)
6288                .policy(policy)
6289                .decrypt_owned_document(&mut document, None),
6290            Err(XmlEncError::Policy(
6291                crate::policy::PolicyViolation::ResourceLimit {
6292                    resource: crate::policy::resource_name::XML_DEPTH,
6293                    maximum: 2,
6294                    actual: 3,
6295                }
6296            ))
6297        ));
6298    }
6299
6300    #[test]
6301    fn fragment_validation_does_not_charge_its_internal_wrapper_node() {
6302        // The caller's node ceiling applies to input and output XML, not the
6303        // implementation-only element used to prove replacement boundaries.
6304        let key = [0x39_u8; 16];
6305        let plaintext = "<item/>".repeat(20);
6306        let encrypted = encrypted_gcm_element(
6307            "http://www.w3.org/2001/04/xmlenc#Content",
6308            &plaintext,
6309            None,
6310            false,
6311            &key,
6312        );
6313        let document = format!("<root xmlns:xenc=\"{XMLENC_NS}\">{encrypted}</root>");
6314        let resolver = SymmetricKeyDecryptor::new(key);
6315        let expected = decrypt_document(&document, None, &resolver)
6316            .expect("unbounded setup decryption must succeed");
6317        let exact_output_nodes = Document::parse(&expected)
6318            .expect("decrypted output must parse")
6319            .descendants()
6320            .count();
6321        let policy = crate::policy::DecryptionPolicy {
6322            resources: crate::policy::ResourcePolicy {
6323                max_xml_nodes: exact_output_nodes,
6324                ..crate::policy::ResourcePolicy::default()
6325            },
6326            ..crate::policy::DecryptionPolicy::default()
6327        };
6328
6329        assert_eq!(
6330            DecryptContext::new(&resolver)
6331                .policy(policy)
6332                .decrypt_document(&document, None)
6333                .expect("temporary wrapper must not consume caller node budget"),
6334            expected
6335        );
6336    }
6337
6338    #[test]
6339    fn owned_decryption_rejects_projected_node_limit_atomically() {
6340        // The owned document may have been parsed under a broader ceiling than
6341        // this operation; expanded plaintext must be bounded before mutation.
6342        let key = [0x3a_u8; 16];
6343        let plaintext = "<item/>".repeat(64);
6344        let encrypted = encrypted_gcm_element(
6345            "http://www.w3.org/2001/04/xmlenc#Content",
6346            &plaintext,
6347            None,
6348            false,
6349            &key,
6350        );
6351        let mut document = XmlDocument::parse(format!(
6352            "<root xmlns:xenc=\"{XMLENC_NS}\">{encrypted}</root>"
6353        ))
6354        .expect("owned encrypted fixture must parse");
6355        let input_nodes = document.with_view(|view| view.node_count());
6356        let before = document.as_xml().to_owned();
6357        let policy = crate::policy::DecryptionPolicy {
6358            resources: crate::policy::ResourcePolicy {
6359                max_xml_nodes: input_nodes,
6360                ..crate::policy::ResourcePolicy::default()
6361            },
6362            ..crate::policy::DecryptionPolicy::default()
6363        };
6364
6365        let error = DecryptContext::new(&SymmetricKeyDecryptor::new(key))
6366            .policy(policy)
6367            .decrypt_owned_document(&mut document, None)
6368            .expect_err("expanded plaintext must exceed the operation node ceiling");
6369
6370        assert!(matches!(
6371            error,
6372            XmlEncError::Policy(crate::policy::PolicyViolation::ResourceLimit {
6373                resource: crate::policy::resource_name::XML_NODES,
6374                maximum,
6375                ..
6376            }) if maximum == input_nodes
6377        ));
6378        assert_eq!(document.as_xml(), before);
6379        assert_eq!(document.generation(), 0);
6380    }
6381
6382    #[test]
6383    fn owned_decryption_reports_decrypted_depth_as_policy() {
6384        // The encrypted envelope can satisfy the active depth policy while its
6385        // plaintext replacement exceeds it. That rejection must retain the
6386        // typed policy contract and leave the owned document untouched.
6387        let key = [0x3b_u8; 16];
6388        let plaintext = format!("{}value{}", "<nested>".repeat(32), "</nested>".repeat(32));
6389        let encrypted = encrypted_gcm_element(
6390            "http://www.w3.org/2001/04/xmlenc#Element",
6391            &plaintext,
6392            None,
6393            false,
6394            &key,
6395        );
6396        let mut document = XmlDocument::parse(format!(
6397            "<root xmlns:xenc=\"{XMLENC_NS}\">{encrypted}</root>"
6398        ))
6399        .expect("encrypted fixture must parse");
6400        let input_depth = document.with_view(|view| view.max_depth());
6401        let before = document.as_xml().to_owned();
6402        let policy = crate::policy::DecryptionPolicy {
6403            resources: crate::policy::ResourcePolicy {
6404                max_xml_depth: input_depth,
6405                ..crate::policy::ResourcePolicy::default()
6406            },
6407            ..crate::policy::DecryptionPolicy::default()
6408        };
6409
6410        let error = DecryptContext::new(&SymmetricKeyDecryptor::new(key))
6411            .policy(policy)
6412            .decrypt_owned_document(&mut document, None)
6413            .expect_err("deep plaintext must exceed the active depth policy");
6414
6415        assert!(matches!(
6416            error,
6417            XmlEncError::Policy(crate::policy::PolicyViolation::ResourceLimit {
6418                resource: crate::policy::resource_name::XML_DEPTH,
6419                maximum,
6420                actual,
6421            }) if maximum == input_depth && actual > maximum
6422        ));
6423        assert_eq!(document.as_xml(), before);
6424        assert_eq!(document.generation(), 0);
6425    }
6426
6427    #[test]
6428    fn validates_replacement_plaintext_in_its_namespace_context() {
6429        // Decrypted fragments inherit namespaces from the encrypted node's
6430        // ancestors, so boundary validation must occur inside the source document.
6431        let key = [0x37_u8; 16];
6432        let encrypted = encrypted_gcm_element(
6433            "http://www.w3.org/2001/04/xmlenc#Content",
6434            "<shared:child/>",
6435            None,
6436            false,
6437            &key,
6438        );
6439        let document = format!(
6440            "<root xmlns:xenc=\"{XMLENC_NS}\" xmlns:shared=\"urn:shared\">{encrypted}</root>"
6441        );
6442        let decrypted = decrypt_document(&document, None, &SymmetricKeyDecryptor::new(key))
6443            .expect("inherited namespace prefixes must remain valid");
6444        assert_eq!(
6445            decrypted,
6446            format!(
6447                "<root xmlns:xenc=\"{XMLENC_NS}\" xmlns:shared=\"urn:shared\"><shared:child/></root>"
6448            )
6449        );
6450    }
6451
6452    fn encrypted_gcm_element(
6453        type_uri: &str,
6454        plaintext: &str,
6455        id: Option<&str>,
6456        declare_namespace: bool,
6457        key: &[u8; 16],
6458    ) -> String {
6459        let nonce = [0x44_u8; 12];
6460        let mut ciphertext = plaintext.as_bytes().to_vec();
6461        Aes128Gcm::new_from_slice(key)
6462            .expect("fixed content key length")
6463            .encrypt_in_place(&nonce.into(), b"", &mut ciphertext)
6464            .expect("test encryption must succeed");
6465        let mut wire = nonce.to_vec();
6466        wire.extend_from_slice(&ciphertext);
6467        let namespace = declare_namespace
6468            .then_some(format!(" xmlns:xenc=\"{XMLENC_NS}\""))
6469            .unwrap_or_default();
6470        let data_type = (!type_uri.is_empty())
6471            .then_some(format!(" Type=\"{type_uri}\""))
6472            .unwrap_or_default();
6473        let id = id
6474            .map(|value| format!(" Id=\"{value}\""))
6475            .unwrap_or_default();
6476        format!(
6477            "<xenc:EncryptedData{namespace}{data_type}{id}><xenc:EncryptionMethod Algorithm=\"http://www.w3.org/2009/xmlenc11#aes128-gcm\"/><xenc:CipherData><xenc:CipherValue>{}</xenc:CipherValue></xenc:CipherData></xenc:EncryptedData>",
6478            STANDARD.encode(wire)
6479        )
6480    }
6481}