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