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 digest(
3677            &self,
3678            algorithm: crate::xmldsig::DigestAlgorithm,
3679            data: &[u8],
3680        ) -> Result<Vec<u8>, crate::provider::ProviderError> {
3681            crate::provider::CryptoProvider::digest(
3682                &crate::provider::RustCryptoProvider,
3683                algorithm,
3684                data,
3685            )
3686        }
3687
3688        #[cfg(feature = "xmldsig")]
3689        fn sign(
3690            &self,
3691            key: &dyn crate::xmldsig::SigningKey,
3692            algorithm: crate::xmldsig::SignatureAlgorithm,
3693            data: &[u8],
3694        ) -> Result<Vec<u8>, crate::xmldsig::SigningKeyError> {
3695            crate::provider::CryptoProvider::sign(
3696                &crate::provider::RustCryptoProvider,
3697                key,
3698                algorithm,
3699                data,
3700            )
3701        }
3702
3703        #[cfg(feature = "xmldsig")]
3704        fn verify(
3705            &self,
3706            key: &dyn crate::xmldsig::VerifyingKey,
3707            algorithm: crate::xmldsig::SignatureAlgorithm,
3708            data: &[u8],
3709            signature: &[u8],
3710        ) -> Result<bool, crate::xmldsig::DsigError> {
3711            crate::provider::CryptoProvider::verify(
3712                &crate::provider::RustCryptoProvider,
3713                key,
3714                algorithm,
3715                data,
3716                signature,
3717            )
3718        }
3719
3720        fn encrypt_data(
3721            &self,
3722            algorithm: DataEncryptionAlgorithm,
3723            key: &[u8],
3724            plaintext: &[u8],
3725        ) -> Result<Vec<u8>, crate::provider::ProviderError> {
3726            crate::provider::CryptoProvider::encrypt_data(
3727                &crate::provider::RustCryptoProvider,
3728                algorithm,
3729                key,
3730                plaintext,
3731            )
3732        }
3733
3734        fn decrypt_data(
3735            &self,
3736            _algorithm: DataEncryptionAlgorithm,
3737            _key: &[u8],
3738            _ciphertext: &[u8],
3739        ) -> Result<Vec<u8>, crate::provider::ProviderError> {
3740            let index = self.decrypt_calls.fetch_add(1, Ordering::Relaxed);
3741            Ok(self
3742                .candidate_plaintexts
3743                .get(index)
3744                .unwrap_or(&self.plaintext)
3745                .clone())
3746        }
3747
3748        fn wrap_key(
3749            &self,
3750            algorithm: KeyWrapAlgorithm,
3751            kek: &[u8],
3752            key: &[u8],
3753        ) -> Result<Vec<u8>, crate::provider::ProviderError> {
3754            crate::provider::CryptoProvider::wrap_key(
3755                &crate::provider::RustCryptoProvider,
3756                algorithm,
3757                kek,
3758                key,
3759            )
3760        }
3761
3762        fn unwrap_key(
3763            &self,
3764            _algorithm: KeyWrapAlgorithm,
3765            _kek: &[u8],
3766            _wrapped: &[u8],
3767        ) -> Result<Vec<u8>, crate::provider::ProviderError> {
3768            self.unwrap_calls.fetch_add(1, Ordering::Relaxed);
3769            Ok(vec![0_u8; 16])
3770        }
3771
3772        fn transport_key(
3773            &self,
3774            key: &dyn crate::provider::KeyTransportKey,
3775            parameters: &RsaOaepParameters,
3776            plaintext: &[u8],
3777        ) -> Result<Vec<u8>, crate::provider::ProviderError> {
3778            crate::provider::CryptoProvider::transport_key(
3779                &crate::provider::RustCryptoProvider,
3780                key,
3781                parameters,
3782                plaintext,
3783            )
3784        }
3785
3786        fn recover_key(
3787            &self,
3788            _key: &dyn crate::provider::KeyRecoveryKey,
3789            _parameters: &RsaOaepParameters,
3790            _ciphertext: &[u8],
3791        ) -> Result<Vec<u8>, crate::provider::ProviderError> {
3792            self.recover_calls.fetch_add(1, Ordering::Relaxed);
3793            Ok(vec![0_u8; 16])
3794        }
3795    }
3796
3797    impl DecryptionKeyResolver for CountingResolver {
3798        fn resolve_encapsulation_content_keys_with_policy(
3799            &self,
3800            _: &dyn crate::provider::CryptoProvider,
3801            _: DataEncryptionAlgorithm,
3802            _: &crate::key_establishment::EncapsulationMechanism,
3803            _: &crate::policy::DecryptionPolicy,
3804            _: &mut KeyCandidateBudget,
3805        ) -> Result<Vec<crate::provider::RecoveredContentKey>, XmlEncError> {
3806            self.candidate_calls.set(self.candidate_calls.get() + 1);
3807            Ok(vec![crate::provider::RecoveredContentKey::confirmed(
3808                self.key.clone(),
3809            )])
3810        }
3811
3812        fn resolve_key_candidates_with_policy(
3813            &self,
3814            provider: &dyn crate::provider::CryptoProvider,
3815            algorithm: DataEncryptionAlgorithm,
3816            encrypted_key: Option<&EncryptedKey>,
3817            policy: &crate::policy::DecryptionPolicy,
3818            budget: &mut KeyCandidateBudget,
3819        ) -> Result<Vec<Vec<u8>>, XmlEncError> {
3820            // Prepared test keys isolate validation-before-lookup ordering.
3821            policy.validate()?;
3822            self.resolve_key_candidates(provider, algorithm, encrypted_key, budget)
3823        }
3824
3825        fn resolve_key(
3826            &self,
3827            _provider: &dyn crate::provider::CryptoProvider,
3828            _algorithm: DataEncryptionAlgorithm,
3829            encrypted_key: Option<&EncryptedKey>,
3830        ) -> Result<Vec<u8>, XmlEncError> {
3831            if encrypted_key.is_some() {
3832                self.candidate_calls.set(self.candidate_calls.get() + 1);
3833                Ok(self.key.clone())
3834            } else {
3835                Err(XmlEncError::KeyNotFound)
3836            }
3837        }
3838    }
3839
3840    impl DecryptionKeyResolver for AllCallsResolver {
3841        fn resolve_key(
3842            &self,
3843            _provider: &dyn crate::provider::CryptoProvider,
3844            _algorithm: DataEncryptionAlgorithm,
3845            _encrypted_key: Option<&EncryptedKey>,
3846        ) -> Result<Vec<u8>, XmlEncError> {
3847            self.calls.set(self.calls.get() + 1);
3848            Ok(self.key.clone())
3849        }
3850    }
3851
3852    impl DecryptionKeyResolver for RecipientKeyResolver {
3853        fn resolve_key_candidates_with_policy(
3854            &self,
3855            provider: &dyn crate::provider::CryptoProvider,
3856            algorithm: DataEncryptionAlgorithm,
3857            encrypted_key: Option<&EncryptedKey>,
3858            policy: &crate::policy::DecryptionPolicy,
3859            budget: &mut KeyCandidateBudget,
3860        ) -> Result<Vec<Vec<u8>>, XmlEncError> {
3861            // Prepared test keys simulate recipient labels, not RSA recovery.
3862            policy.validate()?;
3863            self.resolve_key_candidates(provider, algorithm, encrypted_key, budget)
3864        }
3865
3866        fn resolve_key(
3867            &self,
3868            _provider: &dyn crate::provider::CryptoProvider,
3869            _algorithm: DataEncryptionAlgorithm,
3870            encrypted_key: Option<&EncryptedKey>,
3871        ) -> Result<Vec<u8>, XmlEncError> {
3872            if encrypted_key.and_then(|key| key.recipient.as_deref()) == Some(self.recipient) {
3873                Ok(self.key.clone())
3874            } else {
3875                Err(XmlEncError::KeyNotFound)
3876            }
3877        }
3878    }
3879
3880    #[test]
3881    fn decrypts_gcm_and_rejects_tampering() {
3882        // Authentication must cover the complete ciphertext and tag before plaintext returns.
3883        let key = [7_u8; 16];
3884        let nonce = [9_u8; 12];
3885        let mut ciphertext = b"<Assertion>trusted</Assertion>".to_vec();
3886        Aes128Gcm::new_from_slice(&key)
3887            .expect("fixed key length")
3888            .encrypt_in_place(&nonce.into(), b"", &mut ciphertext)
3889            .expect("test encryption must succeed");
3890        let mut wire = nonce.to_vec();
3891        wire.extend_from_slice(&ciphertext);
3892        let xml = format!(
3893            "<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>",
3894            STANDARD.encode(&wire)
3895        );
3896        let decrypted = decrypt(&xml, &SymmetricKeyDecryptor::new(key))
3897            .expect("valid AES-GCM XML must decrypt");
3898        assert_eq!(
3899            decrypted,
3900            DecryptedContent::Xml("<Assertion>trusted</Assertion>".into())
3901        );
3902        let last = wire.len() - 1;
3903        wire[last] ^= 1;
3904        let tampered = xml.replace(&STANDARD.encode(ciphertext), &STANDARD.encode(&wire[12..]));
3905        assert!(matches!(
3906            decrypt(&tampered, &SymmetricKeyDecryptor::new(key)),
3907            Err(XmlEncError::AeadAuthenticationFailed)
3908        ));
3909    }
3910
3911    #[test]
3912    fn candidate_keys_retry_only_authenticated_decryption() {
3913        // Candidate selection belongs inside one prepared decryption operation:
3914        // structural validation and ciphertext decoding must not be repeated.
3915        let key = [7_u8; 16];
3916        let nonce = [9_u8; 12];
3917        let mut ciphertext = b"candidate plaintext".to_vec();
3918        Aes128Gcm::new_from_slice(&key)
3919            .expect("fixed key length")
3920            .encrypt_in_place(&nonce.into(), b"", &mut ciphertext)
3921            .expect("test encryption must succeed");
3922        let mut wire = nonce.to_vec();
3923        wire.extend_from_slice(&ciphertext);
3924        let encrypted = EncryptedData {
3925            encapsulation_methods: Vec::new(),
3926            id: None,
3927            derived_keys: Vec::new(),
3928            agreement_methods: Vec::new(),
3929            encrypted_type: None,
3930            encryption_method: EncryptionMethod {
3931                chacha: None,
3932                algorithm: DataEncryptionAlgorithm::Aes128Gcm.uri().into(),
3933                key_size_bits: None,
3934                oaep_digest: None,
3935                mgf_algorithm: None,
3936                oaep_params: None,
3937            },
3938            key_name: None,
3939            encrypted_keys: Vec::new(),
3940            cipher_data: CipherData::Value {
3941                value: STANDARD.encode(wire),
3942            },
3943        };
3944        let resolver = CandidateResolver {
3945            keys: vec![vec![1_u8; 16], key.to_vec()],
3946        };
3947
3948        let decrypted = DecryptContext::new(&resolver)
3949            .decrypt_data(&encrypted)
3950            .expect("a later authenticated candidate must decrypt");
3951
3952        assert_eq!(
3953            decrypted,
3954            DecryptedContent::Bytes(b"candidate plaintext".to_vec())
3955        );
3956    }
3957
3958    #[test]
3959    fn standalone_decryption_stops_after_first_successful_candidate() {
3960        // A successful authenticated candidate is the final standalone result;
3961        // later keys must not cause redundant decryptions or retained plaintexts.
3962        let provider = PermissiveUnwrapProvider {
3963            plaintext: b"accepted".to_vec(),
3964            ..PermissiveUnwrapProvider::default()
3965        };
3966        let resolver = CandidateResolver {
3967            keys: vec![vec![1_u8; 16], vec![2_u8; 16], vec![3_u8; 16]],
3968        };
3969        let encrypted = EncryptedData {
3970            encapsulation_methods: Vec::new(),
3971            id: None,
3972            derived_keys: Vec::new(),
3973            agreement_methods: Vec::new(),
3974            encrypted_type: None,
3975            encryption_method: EncryptionMethod {
3976                chacha: None,
3977                algorithm: DataEncryptionAlgorithm::Aes128Gcm.uri().into(),
3978                key_size_bits: None,
3979                oaep_digest: None,
3980                mgf_algorithm: None,
3981                oaep_params: None,
3982            },
3983            key_name: None,
3984            encrypted_keys: Vec::new(),
3985            cipher_data: CipherData::Value {
3986                value: STANDARD.encode([0_u8; 36]),
3987            },
3988        };
3989
3990        let result = DecryptContext::new(&resolver)
3991            .provider(&provider)
3992            .decrypt_data(&encrypted)
3993            .expect("the first successful candidate must be returned");
3994
3995        assert_eq!(result, DecryptedContent::Bytes(b"accepted".to_vec()));
3996        assert_eq!(provider.decrypt_calls.load(Ordering::Relaxed), 1);
3997    }
3998
3999    #[test]
4000    fn document_decryption_discards_rejected_plaintext_before_next_candidate() {
4001        // Replacement validation may reject authenticated plaintext. The next
4002        // key is then tried, but candidates after the first valid XML stay unused.
4003        let provider = PermissiveUnwrapProvider {
4004            candidate_plaintexts: vec![b"<bad".to_vec(), b"<x/>".to_vec(), b"<u/>".to_vec()],
4005            ..PermissiveUnwrapProvider::default()
4006        };
4007        let resolver = CandidateResolver {
4008            keys: vec![vec![1_u8; 16], vec![2_u8; 16], vec![3_u8; 16]],
4009        };
4010        let encrypted = format!(
4011            "<xenc:EncryptedData xmlns:xenc=\"{XMLENC_NS}\" Type=\"{XMLENC_NS}Element\"><xenc:EncryptionMethod Algorithm=\"{}\"/><xenc:CipherData><xenc:CipherValue>{}</xenc:CipherValue></xenc:CipherData></xenc:EncryptedData>",
4012            DataEncryptionAlgorithm::Aes128Gcm.uri(),
4013            STANDARD.encode([0_u8; 32]),
4014        );
4015
4016        let result = DecryptContext::new(&resolver)
4017            .provider(&provider)
4018            .decrypt_document(&encrypted, None)
4019            .expect("a later candidate with valid replacement XML must succeed");
4020
4021        assert_eq!(result, "<x/>");
4022        assert_eq!(provider.decrypt_calls.load(Ordering::Relaxed), 2);
4023    }
4024
4025    #[test]
4026    fn cbc_rejects_multiple_unordered_key_candidates() {
4027        // CBC padding cannot authenticate which candidate key is correct. A
4028        // resolver must select one key from trusted metadata before decryption.
4029        let encrypted = EncryptedData {
4030            encapsulation_methods: Vec::new(),
4031            id: None,
4032            derived_keys: Vec::new(),
4033            agreement_methods: Vec::new(),
4034            encrypted_type: None,
4035            encryption_method: EncryptionMethod {
4036                chacha: None,
4037                algorithm: DataEncryptionAlgorithm::Aes128Cbc.uri().into(),
4038                key_size_bits: None,
4039                oaep_digest: None,
4040                mgf_algorithm: None,
4041                oaep_params: None,
4042            },
4043            key_name: None,
4044            encrypted_keys: Vec::new(),
4045            cipher_data: CipherData::Value {
4046                value: STANDARD.encode(
4047                    crate::provider::default_provider()
4048                        .encrypt_data(
4049                            DataEncryptionAlgorithm::Aes128Cbc,
4050                            &[7_u8; 16],
4051                            b"opaque plaintext",
4052                        )
4053                        .expect("test encryption must succeed"),
4054                ),
4055            },
4056        };
4057        let resolver = CandidateResolver {
4058            keys: vec![vec![1_u8; 16], vec![7_u8; 16]],
4059        };
4060
4061        let error = DecryptContext::new(&resolver)
4062            .decrypt_data(&encrypted)
4063            .expect_err("unauthenticated CBC must not guess among candidate keys");
4064
4065        assert!(matches!(
4066            error,
4067            XmlEncError::AmbiguousKeyCandidates {
4068                algorithm: DataEncryptionAlgorithm::Aes128Cbc,
4069                actual: 2,
4070            }
4071        ));
4072    }
4073
4074    #[test]
4075    fn cbc_accepts_duplicate_copies_of_one_key_identity() {
4076        // Repeated sources containing the same key do not create the ambiguity
4077        // that unauthenticated CBC must reject between distinct key identities.
4078        let key = vec![0x27_u8; 16];
4079        let encrypted = EncryptedData {
4080            encapsulation_methods: Vec::new(),
4081            id: None,
4082            derived_keys: Vec::new(),
4083            agreement_methods: Vec::new(),
4084            encrypted_type: None,
4085            encryption_method: EncryptionMethod {
4086                chacha: None,
4087                algorithm: DataEncryptionAlgorithm::Aes128Cbc.uri().into(),
4088                key_size_bits: None,
4089                oaep_digest: None,
4090                mgf_algorithm: None,
4091                oaep_params: None,
4092            },
4093            key_name: None,
4094            encrypted_keys: Vec::new(),
4095            cipher_data: CipherData::Value {
4096                value: STANDARD.encode(
4097                    crate::provider::default_provider()
4098                        .encrypt_data(
4099                            DataEncryptionAlgorithm::Aes128Cbc,
4100                            &key,
4101                            b"duplicate identity",
4102                        )
4103                        .expect("test encryption must succeed"),
4104                ),
4105            },
4106        };
4107        let resolver = CandidateResolver {
4108            keys: vec![key.clone(), key],
4109        };
4110
4111        assert_eq!(
4112            DecryptContext::new(&resolver)
4113                .decrypt_data(&encrypted)
4114                .expect("one distinct CBC key identity must decrypt"),
4115            DecryptedContent::Bytes(b"duplicate identity".to_vec())
4116        );
4117    }
4118
4119    #[test]
4120    fn candidate_keys_are_bounded_before_cryptographic_processing() {
4121        // A resolver is caller-controlled; its result cannot multiply one
4122        // prepared operation beyond the implementation safety ceiling.
4123        let encrypted = EncryptedData {
4124            encapsulation_methods: Vec::new(),
4125            id: None,
4126            derived_keys: Vec::new(),
4127            agreement_methods: Vec::new(),
4128            encrypted_type: None,
4129            encryption_method: EncryptionMethod {
4130                chacha: None,
4131                algorithm: DataEncryptionAlgorithm::Aes128Gcm.uri().into(),
4132                key_size_bits: None,
4133                oaep_digest: None,
4134                mgf_algorithm: None,
4135                oaep_params: None,
4136            },
4137            key_name: None,
4138            encrypted_keys: Vec::new(),
4139            cipher_data: CipherData::Value {
4140                value: STANDARD.encode(vec![0_u8; 28]),
4141            },
4142        };
4143        let actual = crate::hard_limits::KEY_CANDIDATE_CEILING + 1;
4144        let resolver = CandidateResolver {
4145            keys: vec![vec![0_u8; 16]; actual],
4146        };
4147
4148        let error = DecryptContext::new(&resolver)
4149            .decrypt_data(&encrypted)
4150            .expect_err("oversized candidate sets must fail before decryption");
4151
4152        assert!(matches!(
4153            error,
4154            XmlEncError::Policy(crate::policy::PolicyViolation::ResourceLimit {
4155                resource: crate::policy::resource_name::KEY_CANDIDATES,
4156                maximum: crate::hard_limits::KEY_CANDIDATE_CEILING,
4157                actual: observed,
4158            }) if observed == actual
4159        ));
4160    }
4161
4162    #[test]
4163    fn operation_policy_controls_candidate_budget() {
4164        // A deployment-selected ceiling must reach resolver accounting; the
4165        // hard implementation ceiling is not the effective runtime policy.
4166        let encrypted = EncryptedData {
4167            encapsulation_methods: Vec::new(),
4168            id: None,
4169            derived_keys: Vec::new(),
4170            agreement_methods: Vec::new(),
4171            encrypted_type: None,
4172            encryption_method: EncryptionMethod {
4173                chacha: None,
4174                algorithm: DataEncryptionAlgorithm::Aes128Gcm.uri().into(),
4175                key_size_bits: None,
4176                oaep_digest: None,
4177                mgf_algorithm: None,
4178                oaep_params: None,
4179            },
4180            key_name: None,
4181            encrypted_keys: Vec::new(),
4182            cipher_data: CipherData::Value {
4183                value: STANDARD.encode(vec![0_u8; 28]),
4184            },
4185        };
4186        let resolver = CandidateResolver {
4187            keys: vec![vec![0_u8; 16]; 3],
4188        };
4189        let mut policy = crate::policy::DecryptionPolicy::default();
4190        policy.resources.max_key_candidates = 2;
4191
4192        let error = DecryptContext::new(&resolver)
4193            .policy(policy)
4194            .decrypt_data(&encrypted)
4195            .expect_err("candidate accounting must use the operation policy ceiling");
4196
4197        assert!(matches!(
4198            error,
4199            XmlEncError::Policy(crate::policy::PolicyViolation::ResourceLimit {
4200                resource: crate::policy::resource_name::KEY_CANDIDATES,
4201                maximum: 2,
4202                actual: 3,
4203            })
4204        ));
4205    }
4206
4207    #[test]
4208    fn candidate_work_ceiling_is_shared_across_recipients() {
4209        // The candidate ceiling bounds the complete decryption operation, not
4210        // each EncryptedKey independently.
4211        let key = vec![0x39_u8; 16];
4212        let resolver = AggregateRecipientResolver {
4213            attempts: Cell::new(0),
4214            key: key.clone(),
4215        };
4216        let encrypted = encrypted_data_with_recipients(
4217            &key,
4218            vec![
4219                associated_encrypted_key("first", None, None),
4220                associated_encrypted_key("second", None, None),
4221            ],
4222            None,
4223        );
4224
4225        DecryptContext::new(&resolver)
4226            .decrypt_data(&encrypted)
4227            .expect_err("a second recipient must not receive a fresh candidate allowance");
4228        assert_eq!(
4229            resolver.attempts.get(),
4230            crate::hard_limits::KEY_CANDIDATE_CEILING
4231        );
4232    }
4233
4234    #[test]
4235    fn candidate_budget_exhaustion_is_fatal_after_a_key_was_found() {
4236        // Candidate exhaustion must remain terminal even when it occurs in a
4237        // resolver path dedicated to one particular key family.
4238        let key = vec![0x49_u8; 16];
4239        let resolver = MislabelledExhaustionResolver {
4240            direct: key.clone(),
4241        };
4242        let encrypted = encrypted_data_with_recipients(
4243            &key,
4244            vec![associated_encrypted_key("recipient", None, None)],
4245            None,
4246        );
4247
4248        let error = DecryptContext::new(&resolver)
4249            .decrypt_data(&encrypted)
4250            .expect_err("candidate exhaustion must override an earlier usable key");
4251
4252        assert!(matches!(
4253            error,
4254            XmlEncError::Policy(crate::policy::PolicyViolation::ResourceLimit {
4255                resource: "key candidates",
4256                maximum: crate::hard_limits::KEY_CANDIDATE_CEILING,
4257                actual,
4258            }) if actual == crate::hard_limits::KEY_CANDIDATE_CEILING + 1
4259        ));
4260    }
4261
4262    #[test]
4263    fn authenticated_decryption_continues_after_wrong_unwrapped_recipient_key() {
4264        // A same-width key can unwrap successfully yet fail GCM authentication;
4265        // later applicable recipients must remain available to the data cipher.
4266        let correct = vec![0x53_u8; 16];
4267        let encrypted = encrypted_data_with_recipients(
4268            &correct,
4269            vec![
4270                associated_encrypted_key("first", None, None),
4271                associated_encrypted_key("second", None, None),
4272            ],
4273            None,
4274        );
4275        let resolver = OrderedRecipientResolver {
4276            wrong: vec![0x11_u8; 16],
4277            correct,
4278        };
4279
4280        let plaintext = DecryptContext::new(&resolver)
4281            .decrypt_data(&encrypted)
4282            .expect("the second recipient key must authenticate");
4283        assert_eq!(plaintext, DecryptedContent::Bytes(b"payload".to_vec()));
4284    }
4285
4286    #[test]
4287    fn authenticated_decryption_continues_from_direct_key_to_recipient() {
4288        // Direct candidates and embedded recipients are one ordered lookup
4289        // space; a wrong direct GCM key must not hide a valid wrapped key.
4290        let correct = vec![0x63_u8; 16];
4291        let encrypted = encrypted_data_with_recipients(
4292            &correct,
4293            vec![associated_encrypted_key("recipient", None, None)],
4294            None,
4295        );
4296        let resolver = DirectAndRecipientResolver {
4297            direct: vec![0x19_u8; 16],
4298            recipient: correct,
4299        };
4300
4301        let plaintext = DecryptContext::new(&resolver)
4302            .decrypt_data(&encrypted)
4303            .expect("the embedded recipient must remain available after a direct candidate");
4304
4305        assert_eq!(plaintext, DecryptedContent::Bytes(b"payload".to_vec()));
4306    }
4307
4308    #[test]
4309    fn authenticated_decryption_continues_after_direct_lookup_error() {
4310        // A candidate-local direct lookup failure must not suppress a valid
4311        // embedded recipient from the same ordered key-resolution operation.
4312        let correct = vec![0x64_u8; 16];
4313        let encrypted = encrypted_data_with_recipients(
4314            &correct,
4315            vec![associated_encrypted_key("recipient", None, None)],
4316            None,
4317        );
4318        let resolver = FailingDirectResolver { recipient: correct };
4319
4320        let plaintext = DecryptContext::new(&resolver)
4321            .decrypt_data(&encrypted)
4322            .expect("recipient lookup must follow a candidate-local direct error");
4323
4324        assert_eq!(plaintext, DecryptedContent::Bytes(b"payload".to_vec()));
4325    }
4326
4327    #[test]
4328    fn resolver_policy_rejection_stops_before_later_recipient() {
4329        // A custom resolver's typed policy rejection is operation-wide; trying
4330        // a later recipient after it would allow key-source policy bypass.
4331        let correct = vec![0x65_u8; 16];
4332        let encrypted = encrypted_data_with_recipients(
4333            &correct,
4334            vec![associated_encrypted_key("recipient", None, None)],
4335            None,
4336        );
4337        let resolver = PolicyRejectingDirectResolver { recipient: correct };
4338
4339        let error = DecryptContext::new(&resolver)
4340            .decrypt_data(&encrypted)
4341            .expect_err("operation policy rejection must be fatal");
4342
4343        assert!(matches!(
4344            error,
4345            XmlEncError::Policy(crate::policy::PolicyViolation::KeyTrust {
4346                reason: "test resolver rejected the operation",
4347            })
4348        ));
4349    }
4350
4351    #[test]
4352    fn cbc_rejects_distinct_direct_and_recipient_candidates() {
4353        // Combining lookup sources must not make unauthenticated CBC choose the
4354        // direct key merely because it was resolved before the recipient key.
4355        let recipient = vec![0x73_u8; 16];
4356        let mut encrypted = encrypted_data_with_recipients(
4357            &recipient,
4358            vec![associated_encrypted_key("recipient", None, None)],
4359            None,
4360        );
4361        encrypted.encryption_method.algorithm = DataEncryptionAlgorithm::Aes128Cbc.uri().into();
4362        *encrypted
4363            .cipher_data
4364            .inline_value_mut()
4365            .expect("inline ciphertext") = STANDARD.encode(
4366            crate::provider::default_provider()
4367                .encrypt_data(DataEncryptionAlgorithm::Aes128Cbc, &recipient, b"payload")
4368                .expect("test encryption must succeed"),
4369        );
4370        let resolver = DirectAndRecipientResolver {
4371            direct: vec![0x29_u8; 16],
4372            recipient,
4373        };
4374
4375        let error = DecryptContext::new(&resolver)
4376            .decrypt_data(&encrypted)
4377            .expect_err("CBC must not guess between direct and recipient keys");
4378
4379        assert!(matches!(
4380            error,
4381            XmlEncError::AmbiguousKeyCandidates {
4382                algorithm: DataEncryptionAlgorithm::Aes128Cbc,
4383                actual: 2,
4384            }
4385        ));
4386    }
4387
4388    #[test]
4389    fn cbc_ambiguity_ignores_algorithm_incompatible_key_widths() {
4390        // A wrong-width key cannot reach AES-CBC and therefore cannot make one
4391        // width-compatible candidate ambiguous.
4392        let key = vec![0x47_u8; 16];
4393        let ciphertext = crate::provider::default_provider()
4394            .encrypt_data(DataEncryptionAlgorithm::Aes128Cbc, &key, b"payload")
4395            .expect("test encryption must succeed");
4396        let encrypted = EncryptedData {
4397            encapsulation_methods: Vec::new(),
4398            id: None,
4399            derived_keys: Vec::new(),
4400            agreement_methods: Vec::new(),
4401            encrypted_type: None,
4402            encryption_method: EncryptionMethod {
4403                chacha: None,
4404                algorithm: DataEncryptionAlgorithm::Aes128Cbc.uri().into(),
4405                key_size_bits: None,
4406                oaep_digest: None,
4407                mgf_algorithm: None,
4408                oaep_params: None,
4409            },
4410            key_name: None,
4411            encrypted_keys: Vec::new(),
4412            cipher_data: CipherData::Value {
4413                value: STANDARD.encode(ciphertext),
4414            },
4415        };
4416        let resolver = CandidateResolver {
4417            keys: vec![vec![0_u8; 32], key],
4418        };
4419
4420        assert_eq!(
4421            DecryptContext::new(&resolver)
4422                .decrypt_data(&encrypted)
4423                .expect("the sole width-compatible CBC key must be selected"),
4424            DecryptedContent::Bytes(b"payload".to_vec())
4425        );
4426    }
4427
4428    #[test]
4429    fn data_reference_selects_the_associated_encrypted_key() {
4430        // An explicit DataReference to another object contradicts this
4431        // EncryptedData even when that recipient can be unwrapped.
4432        let key = vec![0x51_u8; 16];
4433        let resolver = AssociationRecordingResolver {
4434            visited: RefCell::new(Vec::new()),
4435            key: key.clone(),
4436        };
4437        let encrypted = encrypted_data_with_recipients(
4438            &key,
4439            vec![
4440                associated_encrypted_key("unrelated", Some("#other"), None),
4441                associated_encrypted_key("matching", Some("#target"), None),
4442            ],
4443            None,
4444        );
4445
4446        DecryptContext::new(&resolver)
4447            .decrypt_data(&encrypted)
4448            .expect("the associated recipient must decrypt");
4449        assert_eq!(resolver.visited.into_inner(), ["matching"]);
4450    }
4451
4452    #[test]
4453    fn carried_key_name_selects_the_named_content_key() {
4454        // CarriedKeyName identifies the transported key named by the enclosing
4455        // EncryptedData KeyInfo; a contradictory label must be skipped.
4456        let key = vec![0x52_u8; 16];
4457        let resolver = AssociationRecordingResolver {
4458            visited: RefCell::new(Vec::new()),
4459            key: key.clone(),
4460        };
4461        let encrypted = encrypted_data_with_recipients(
4462            &key,
4463            vec![
4464                associated_encrypted_key("unrelated", None, Some("other")),
4465                associated_encrypted_key("matching", None, Some("wanted")),
4466            ],
4467            Some("wanted"),
4468        );
4469
4470        DecryptContext::new(&resolver)
4471            .decrypt_data(&encrypted)
4472            .expect("the matching carried key name must decrypt");
4473        assert_eq!(resolver.visited.into_inner(), ["matching"]);
4474    }
4475
4476    #[test]
4477    fn contradictory_encrypted_key_associations_fail_closed() {
4478        // Association metadata is authoritative when present. The resolver must
4479        // not see a recipient that explicitly names another encrypted object.
4480        let key = vec![0x53_u8; 16];
4481        let resolver = AssociationRecordingResolver {
4482            visited: RefCell::new(Vec::new()),
4483            key: key.clone(),
4484        };
4485        let encrypted = encrypted_data_with_recipients(
4486            &key,
4487            vec![associated_encrypted_key("unrelated", Some("#other"), None)],
4488            None,
4489        );
4490
4491        assert!(matches!(
4492            DecryptContext::new(&resolver).decrypt_data(&encrypted),
4493            Err(XmlEncError::KeyNotFound)
4494        ));
4495        assert!(resolver.visited.into_inner().is_empty());
4496    }
4497
4498    #[test]
4499    fn direct_symmetric_key_ignores_embedded_key_hints() {
4500        // A caller-supplied content key is authoritative for this resolver;
4501        // unrelated recipient hints must not disable direct-key decryption.
4502        let key = [0x28_u8; 16];
4503        let unrelated = EncryptedKey {
4504            sources: Default::default(),
4505            id: None,
4506            recipient: Some("other-recipient".into()),
4507            key_name: None,
4508            encryption_method: super::super::EncryptionMethod {
4509                chacha: None,
4510                algorithm: "urn:unrelated:key-transport".into(),
4511                key_size_bits: None,
4512                oaep_digest: None,
4513                mgf_algorithm: None,
4514                oaep_params: None,
4515            },
4516            cipher_data: super::super::CipherData::Value {
4517                value: STANDARD.encode([0_u8; 24]),
4518            },
4519            reference_list: None,
4520            carried_key_name: None,
4521        };
4522
4523        assert_eq!(
4524            SymmetricKeyDecryptor::new(key)
4525                .resolve_key(
4526                    crate::provider::default_provider(),
4527                    DataEncryptionAlgorithm::Aes128Gcm,
4528                    Some(&unrelated)
4529                )
4530                .expect("direct key must ignore unrelated embedded hints"),
4531            key
4532        );
4533    }
4534
4535    #[test]
4536    fn decrypts_with_the_matching_recipient_key() {
4537        // Multi-recipient KeyInfo must retain document order and continue after a
4538        // malformed unrelated key before accepting the intended one.
4539        let key = [0x29_u8; 16];
4540        let plaintext = "recipient-specific plaintext";
4541        let encrypted = encrypted_gcm_element("", plaintext, None, true, &key);
4542        let recipient_key = |recipient: &str, method: &str| {
4543            format!(
4544                "<xenc:EncryptedKey Recipient=\"{recipient}\"><xenc:EncryptionMethod Algorithm=\"{method}\">{}</xenc:EncryptionMethod><xenc:CipherData><xenc:CipherValue>YQ==</xenc:CipherValue></xenc:CipherData></xenc:EncryptedKey>",
4545                if recipient == "alice" {
4546                    "<ds:DigestMethod Algorithm=\"urn:unsupported:digest\"/>"
4547                } else {
4548                    ""
4549                }
4550            )
4551        };
4552        let key_info = format!(
4553            "<ds:KeyInfo xmlns:ds=\"{}\">{}{}</ds:KeyInfo>",
4554            crate::xmlenc::types::XMLDSIG_NS,
4555            recipient_key("alice", KeyTransportAlgorithm::RsaOaep11.uri()),
4556            recipient_key("bob", KeyWrapAlgorithm::AesKw128.uri())
4557        );
4558        let xml = encrypted.replacen(
4559            "<xenc:CipherData>",
4560            &format!("{key_info}<xenc:CipherData>"),
4561            1,
4562        );
4563        let resolver = RecipientKeyResolver {
4564            recipient: "bob",
4565            key: key.to_vec(),
4566        };
4567
4568        assert_eq!(
4569            decrypt(&xml, &resolver).expect("second recipient key must be tried"),
4570            DecryptedContent::Bytes(plaintext.as_bytes().to_vec())
4571        );
4572    }
4573
4574    #[test]
4575    fn decryption_policy_bounds_recipients_before_key_resolution() {
4576        // Both XML parsing and caller-constructed typed input must reject an
4577        // oversized recipient set before any resolver can inspect candidates.
4578        let key = [0x29_u8; 16];
4579        let encrypted = encrypted_gcm_element("", "bounded recipients", None, true, &key);
4580        let recipient_key = |recipient: &str| {
4581            format!(
4582                "<xenc:EncryptedKey Recipient=\"{recipient}\"><xenc:EncryptionMethod Algorithm=\"urn:test:key\"/><xenc:CipherData><xenc:CipherValue>YQ==</xenc:CipherValue></xenc:CipherData></xenc:EncryptedKey>"
4583            )
4584        };
4585        let key_info = format!(
4586            "<ds:KeyInfo xmlns:ds=\"{}\">{}{}</ds:KeyInfo>",
4587            crate::xmlenc::types::XMLDSIG_NS,
4588            recipient_key("alice"),
4589            recipient_key("bob")
4590        );
4591        let xml = encrypted.replacen(
4592            "<xenc:CipherData>",
4593            &format!("{key_info}<xenc:CipherData>"),
4594            1,
4595        );
4596        let parsed = parse_encrypted_data(&xml).expect("default parser accepts two recipients");
4597        let policy = crate::policy::DecryptionPolicy {
4598            resources: crate::policy::ResourcePolicy {
4599                max_encryption_recipients: 1,
4600                ..crate::policy::ResourcePolicy::default()
4601            },
4602            ..crate::policy::DecryptionPolicy::default()
4603        };
4604        let resolver = SymmetricKeyDecryptor::new(key);
4605        let context = DecryptContext::new(&resolver).policy(policy);
4606
4607        for error in [
4608            context
4609                .decrypt(&xml)
4610                .expect_err("XML recipient collection must be bounded"),
4611            context
4612                .decrypt_data(&parsed)
4613                .expect_err("typed recipient collection must be bounded"),
4614        ] {
4615            assert!(matches!(
4616                error,
4617                XmlEncError::Policy(crate::policy::PolicyViolation::ResourceLimit {
4618                    resource: crate::policy::resource_name::ENCRYPTION_RECIPIENTS,
4619                    maximum: 1,
4620                    actual: 2,
4621                })
4622            ));
4623        }
4624    }
4625
4626    #[test]
4627    fn decrypts_session_key_wrapped_with_aes_kw() {
4628        // RFC 3394 unwrap must recover exactly the content algorithm's key length.
4629        let kek = [3_u8; 16];
4630        let session_key = [4_u8; 16];
4631        let mut wrapped = [0_u8; 24];
4632        KwAes128::new_from_slice(&kek)
4633            .expect("fixed KEK length")
4634            .wrap_key(&session_key, &mut wrapped)
4635            .expect("RFC 3394 test wrapping must succeed");
4636        let encrypted_key = EncryptedKey {
4637            id: None,
4638            sources: Default::default(),
4639            recipient: None,
4640            key_name: None,
4641            encryption_method: super::super::EncryptionMethod {
4642                chacha: None,
4643                algorithm: "http://www.w3.org/2001/04/xmlenc#kw-aes128".into(),
4644                key_size_bits: None,
4645                oaep_digest: None,
4646                mgf_algorithm: None,
4647                oaep_params: None,
4648            },
4649            cipher_data: super::super::CipherData::Value {
4650                value: STANDARD.encode(wrapped),
4651            },
4652            reference_list: None,
4653            carried_key_name: None,
4654        };
4655        let resolved = KekDecryptor::new(kek)
4656            .resolve_key(
4657                crate::provider::default_provider(),
4658                DataEncryptionAlgorithm::Aes128Gcm,
4659                Some(&encrypted_key),
4660            )
4661            .expect("wrapped session key must resolve");
4662        assert_eq!(resolved, session_key);
4663    }
4664
4665    #[test]
4666    fn rejects_invalid_kek_before_custom_provider_dispatch() {
4667        // KEK length is part of the XMLEnc algorithm contract, not a provider
4668        // preference. A permissive provider must not bypass facade validation.
4669        let encrypted_key = EncryptedKey {
4670            id: None,
4671            sources: Default::default(),
4672            recipient: None,
4673            key_name: None,
4674            encryption_method: super::super::EncryptionMethod {
4675                chacha: None,
4676                algorithm: KeyWrapAlgorithm::AesKw128.uri().into(),
4677                key_size_bits: None,
4678                oaep_digest: None,
4679                mgf_algorithm: None,
4680                oaep_params: None,
4681            },
4682            cipher_data: super::super::CipherData::Value {
4683                value: STANDARD.encode([0_u8; 24]),
4684            },
4685            reference_list: None,
4686            carried_key_name: None,
4687        };
4688        let provider = PermissiveUnwrapProvider::default();
4689
4690        assert!(matches!(
4691            KekDecryptor::new([0_u8; 32]).resolve_key(
4692                &provider,
4693                DataEncryptionAlgorithm::Aes128Gcm,
4694                Some(&encrypted_key),
4695            ),
4696            Err(XmlEncError::InvalidKekSize {
4697                algorithm: KeyWrapAlgorithm::AesKw128,
4698                expected: 16,
4699                actual: 32,
4700            })
4701        ));
4702        assert_eq!(provider.unwrap_calls.load(Ordering::Relaxed), 0);
4703    }
4704
4705    #[test]
4706    fn rejects_content_ciphertext_framing_before_resolution_or_provider_dispatch() {
4707        // Algorithm framing belongs to the XMLEnc facade. A permissive provider
4708        // and resolver must never observe malformed standard CipherValue bytes.
4709        for (algorithm, ciphertext_len) in [
4710            (DataEncryptionAlgorithm::Aes128Gcm, 27),
4711            (DataEncryptionAlgorithm::Aes128Cbc, 33),
4712        ] {
4713            let resolver = AllCallsResolver {
4714                calls: Cell::new(0),
4715                key: vec![0_u8; algorithm.key_len()],
4716            };
4717            let provider = PermissiveUnwrapProvider::default();
4718            let encrypted = EncryptedData {
4719                encapsulation_methods: Vec::new(),
4720                id: None,
4721                derived_keys: Vec::new(),
4722                agreement_methods: Vec::new(),
4723                encrypted_type: None,
4724                key_name: None,
4725                encryption_method: super::super::EncryptionMethod {
4726                    chacha: None,
4727                    algorithm: algorithm.uri().into(),
4728                    key_size_bits: None,
4729                    oaep_digest: None,
4730                    mgf_algorithm: None,
4731                    oaep_params: None,
4732                },
4733                encrypted_keys: Vec::new(),
4734                cipher_data: super::super::CipherData::Value {
4735                    value: STANDARD.encode(vec![0_u8; ciphertext_len]),
4736                },
4737            };
4738
4739            assert!(
4740                DecryptContext::new(&resolver)
4741                    .provider(&provider)
4742                    .decrypt_data(&encrypted)
4743                    .is_err()
4744            );
4745            assert_eq!(resolver.calls.get(), 0);
4746            assert_eq!(provider.decrypt_calls.load(Ordering::Relaxed), 0);
4747        }
4748    }
4749
4750    #[test]
4751    fn rejects_custom_provider_plaintext_outside_algorithm_bounds() {
4752        // A provider success result is still untrusted: GCM fixes the plaintext
4753        // length exactly, while CBC padding permits only one block-sized range.
4754        for (algorithm, ciphertext_len, plaintext_len) in [
4755            (DataEncryptionAlgorithm::Aes128Gcm, 32, 5),
4756            (DataEncryptionAlgorithm::Aes128Cbc, 32, 16),
4757        ] {
4758            let resolver = AllCallsResolver {
4759                calls: Cell::new(0),
4760                key: vec![0_u8; algorithm.key_len()],
4761            };
4762            let provider = PermissiveUnwrapProvider {
4763                plaintext: vec![0_u8; plaintext_len],
4764                ..PermissiveUnwrapProvider::default()
4765            };
4766            let encrypted = EncryptedData {
4767                encapsulation_methods: Vec::new(),
4768                id: None,
4769                derived_keys: Vec::new(),
4770                agreement_methods: Vec::new(),
4771                encrypted_type: None,
4772                key_name: None,
4773                encryption_method: super::super::EncryptionMethod {
4774                    chacha: None,
4775                    algorithm: algorithm.uri().into(),
4776                    key_size_bits: None,
4777                    oaep_digest: None,
4778                    mgf_algorithm: None,
4779                    oaep_params: None,
4780                },
4781                encrypted_keys: Vec::new(),
4782                cipher_data: super::super::CipherData::Value {
4783                    value: STANDARD.encode(vec![0_u8; ciphertext_len]),
4784                },
4785            };
4786
4787            let error = DecryptContext::new(&resolver)
4788                .provider(&provider)
4789                .decrypt_data(&encrypted)
4790                .expect_err("impossible provider output length must fail");
4791            match algorithm {
4792                DataEncryptionAlgorithm::Aes128Gcm => assert!(matches!(
4793                    error,
4794                    XmlEncError::Provider(crate::provider::ProviderError::InvalidOutputSize {
4795                        operation: crate::provider::ProviderOperation::Decrypt,
4796                        expected: 4,
4797                        actual: 5,
4798                    })
4799                )),
4800                DataEncryptionAlgorithm::Aes128Cbc => assert!(matches!(
4801                    error,
4802                    XmlEncError::Provider(crate::provider::ProviderError::InvalidOutputSizeRange {
4803                        operation: crate::provider::ProviderOperation::Decrypt,
4804                        minimum: 0,
4805                        maximum: 15,
4806                        actual: 16,
4807                    })
4808                )),
4809                _ => unreachable!("the regression table covers one GCM and one CBC algorithm"),
4810            }
4811            assert_eq!(provider.decrypt_calls.load(Ordering::Relaxed), 1);
4812        }
4813    }
4814
4815    #[test]
4816    fn rejects_malformed_aes_kw_before_custom_provider_dispatch() {
4817        // RFC 3394 adds exactly eight bytes to the transported content key;
4818        // permissive custom providers must not redefine that wire contract.
4819        let provider = PermissiveUnwrapProvider::default();
4820        for actual in [0, 23, 25] {
4821            let encrypted_key = EncryptedKey {
4822                sources: Default::default(),
4823                id: None,
4824                recipient: None,
4825                key_name: None,
4826                encryption_method: super::super::EncryptionMethod {
4827                    chacha: None,
4828                    algorithm: KeyWrapAlgorithm::AesKw128.uri().into(),
4829                    key_size_bits: None,
4830                    oaep_digest: None,
4831                    mgf_algorithm: None,
4832                    oaep_params: None,
4833                },
4834                cipher_data: super::super::CipherData::Value {
4835                    value: STANDARD.encode(vec![0_u8; actual]),
4836                },
4837                reference_list: None,
4838                carried_key_name: None,
4839            };
4840            assert!(matches!(
4841                KekDecryptor::new([0_u8; 16]).resolve_key(
4842                    &provider,
4843                    DataEncryptionAlgorithm::Aes128Gcm,
4844                    Some(&encrypted_key),
4845                ),
4846                Err(XmlEncError::InvalidWrappedKeyLength {
4847                    expected: 24,
4848                    actual: output_len,
4849                }) if output_len == actual
4850            ));
4851        }
4852        assert_eq!(provider.unwrap_calls.load(Ordering::Relaxed), 0);
4853    }
4854
4855    #[test]
4856    fn rejects_malformed_rsa_oaep_before_custom_provider_dispatch() {
4857        // RSA ciphertext width is the private modulus width, so malformed
4858        // transport bytes must be rejected before provider-owned recovery.
4859        let private_key = RsaPrivateKey::from_pkcs8_pem(include_str!(
4860            "../../tests/fixtures/keys/rsa/rsa-2048-key.pem"
4861        ))
4862        .expect("RSA donor private key must parse");
4863        let provider = PermissiveUnwrapProvider::default();
4864        for actual in [0, 255, 257] {
4865            assert!(matches!(
4866                recover_rsa_oaep(
4867                    &provider,
4868                    &private_key,
4869                    &RsaOaepParameters::default(),
4870                    &vec![0_u8; actual],
4871                    DataEncryptionAlgorithm::Aes128Gcm,
4872                ),
4873                Err(XmlEncError::InvalidWrappedKeyLength {
4874                    expected: 256,
4875                    actual: output_len,
4876                }) if output_len == actual
4877            ));
4878        }
4879        assert_eq!(provider.recover_calls.load(Ordering::Relaxed), 0);
4880    }
4881
4882    #[cfg(feature = "legacy-algorithms")]
4883    #[test]
4884    fn invalid_rsa_recovery_cannot_release_successful_cbc_plaintext() {
4885        // Force the CBC provider to accept its padding, making this regression
4886        // deterministic instead of waiting for random fallback plaintext.
4887        let key = RsaPrivateKey::from_pkcs8_pem(include_str!(
4888            "../../tests/fixtures/keys/rsa/rsa-2048-key.pem"
4889        ))
4890        .expect("valid RSA fixture");
4891        let resolver = PrivateKeyDecryptor::new(key);
4892        for algorithm in [
4893            DataEncryptionAlgorithm::Aes128Cbc,
4894            DataEncryptionAlgorithm::Aes192Cbc,
4895            DataEncryptionAlgorithm::Aes256Cbc,
4896            DataEncryptionAlgorithm::TripleDesCbc,
4897            DataEncryptionAlgorithm::Aes128Gcm,
4898        ] {
4899            let provider = PermissiveUnwrapProvider {
4900                plaintext: b"accepted CBC plaintext".to_vec(),
4901                ..PermissiveUnwrapProvider::default()
4902            };
4903            let mut encrypted = encrypted_data_with_recipients(
4904                &[0; 16],
4905                vec![associated_encrypted_key("recipient", None, None)],
4906                None,
4907            );
4908            encrypted.encryption_method.algorithm = algorithm.uri().into();
4909            *encrypted
4910                .cipher_data
4911                .inline_value_mut()
4912                .expect("inline ciphertext") =
4913                STANDARD.encode(vec![
4914                    0;
4915                    algorithm
4916                        .ciphertext_len_for_plaintext(provider.plaintext.len())
4917                        .expect("bounded test frame")
4918                ]);
4919            encrypted.encrypted_keys[0].encryption_method.algorithm =
4920                KeyTransportAlgorithm::RsaPkcs1v15.uri().into();
4921            *encrypted.encrypted_keys[0]
4922                .cipher_data
4923                .inline_value_mut()
4924                .expect("inline wrapped key") = STANDARD.encode([0; 256]);
4925            let result = DecryptContext::new(&resolver)
4926                .provider(&provider)
4927                .policy(crate::policy::DecryptionPolicy {
4928                    data_algorithms: Some([algorithm].into()),
4929                    key_transport_algorithms: Some([KeyTransportAlgorithm::RsaPkcs1v15].into()),
4930                    ..crate::policy::DecryptionPolicy::default()
4931                })
4932                .decrypt_data(&encrypted);
4933            assert!(
4934                result.is_err(),
4935                "invalid RSA padding must never release fallback plaintext"
4936            );
4937            assert_eq!(
4938                provider.decrypt_calls.load(Ordering::Relaxed),
4939                1,
4940                "content decryption must run before rejecting the recovery"
4941            );
4942        }
4943    }
4944
4945    #[test]
4946    fn candidate_filter_reuses_storage_and_preserves_precedence() {
4947        // Compact invalid widths and non-adjacent duplicates in place without
4948        // reordering distinct candidates or allocating a second candidate list.
4949        let keys = [
4950            vec![0; 8],
4951            vec![2; 16],
4952            vec![3; 16],
4953            vec![2; 16],
4954            vec![4; 32],
4955            vec![5; 16],
4956        ]
4957        .into_iter()
4958        .map(crate::provider::RecoveredContentKey::confirmed)
4959        .collect::<Vec<_>>();
4960        let allocation = keys.as_ptr();
4961        let keys = compatible_decryption_key_candidates(DataEncryptionAlgorithm::Aes128Gcm, keys)
4962            .expect("compatible candidates survive");
4963        assert_eq!(keys.as_ptr(), allocation);
4964        assert_eq!(keys.len(), 3);
4965        for (key, expected) in keys.iter().zip([2, 3, 5]) {
4966            assert_eq!(key.bytes(), &[expected; 16]);
4967        }
4968    }
4969
4970    #[test]
4971    fn custom_provider_recovers_with_an_opaque_private_key() {
4972        // The resolver knows only the public ciphertext width; private key
4973        // material remains entirely behind the provider/key-handle boundary.
4974        let encrypted_key = EncryptedKey {
4975            id: None,
4976            sources: Default::default(),
4977            recipient: None,
4978            key_name: None,
4979            encryption_method: super::super::EncryptionMethod {
4980                chacha: None,
4981                algorithm: KeyTransportAlgorithm::RsaOaep11.uri().into(),
4982                key_size_bits: None,
4983                oaep_digest: Some(OaepDigestAlgorithm::Sha256.uri().into()),
4984                mgf_algorithm: Some(
4985                    OaepDigestAlgorithm::Sha256
4986                        .mgf_uri()
4987                        .expect("MGF1-SHA256 URI")
4988                        .into(),
4989                ),
4990                oaep_params: None,
4991            },
4992            cipher_data: super::super::CipherData::Value {
4993                value: STANDARD.encode(vec![0x5a; 256]),
4994            },
4995            reference_list: None,
4996            carried_key_name: None,
4997        };
4998        let provider = PermissiveUnwrapProvider::default();
4999        let decryptor = PrivateKeyDecryptor::provider_key(Arc::new(OpaqueRecoveryKey));
5000
5001        let key = decryptor
5002            .resolve_key(
5003                &provider,
5004                DataEncryptionAlgorithm::Aes128Gcm,
5005                Some(&encrypted_key),
5006            )
5007            .expect("custom provider must recover through its opaque private key");
5008
5009        assert_eq!(key, vec![0_u8; 16]);
5010        assert_eq!(provider.recover_calls.load(Ordering::Relaxed), 1);
5011    }
5012
5013    #[test]
5014    fn rejects_truncated_gcm_and_invalid_wrapped_key() {
5015        // Framing and key-wrap integrity failures must occur before content is exposed.
5016        assert!(matches!(
5017            crate::provider::default_provider().decrypt_data(
5018                DataEncryptionAlgorithm::Aes128Gcm,
5019                &[0_u8; 16],
5020                &[0_u8; 27],
5021            ),
5022            Err(crate::provider::ProviderError::InvalidInput(
5023                crate::provider::ProviderInputError::AesGcmFraming
5024            ))
5025        ));
5026        let truncated = EncryptedData {
5027            encapsulation_methods: Vec::new(),
5028            id: None,
5029            derived_keys: Vec::new(),
5030            agreement_methods: Vec::new(),
5031            encrypted_type: None,
5032            key_name: None,
5033            encryption_method: super::super::EncryptionMethod {
5034                chacha: None,
5035                algorithm: DataEncryptionAlgorithm::Aes128Gcm.uri().into(),
5036                key_size_bits: None,
5037                oaep_digest: None,
5038                mgf_algorithm: None,
5039                oaep_params: None,
5040            },
5041            encrypted_keys: Vec::new(),
5042            cipher_data: super::super::CipherData::Value {
5043                value: STANDARD.encode([0_u8; 27]),
5044            },
5045        };
5046        assert!(matches!(
5047            DecryptContext::new(&SymmetricKeyDecryptor::new([0_u8; 16])).decrypt_data(&truncated),
5048            Err(XmlEncError::DataTooShort {
5049                algorithm: "AES-GCM",
5050                actual: 27,
5051                ..
5052            })
5053        ));
5054        let encrypted_key = EncryptedKey {
5055            id: None,
5056            sources: Default::default(),
5057            recipient: None,
5058            key_name: None,
5059            encryption_method: super::super::EncryptionMethod {
5060                chacha: None,
5061                algorithm: "http://www.w3.org/2001/04/xmlenc#kw-aes128".into(),
5062                key_size_bits: None,
5063                oaep_digest: None,
5064                mgf_algorithm: None,
5065                oaep_params: None,
5066            },
5067            cipher_data: super::super::CipherData::Value {
5068                value: STANDARD.encode([0_u8; 24]),
5069            },
5070            reference_list: None,
5071            carried_key_name: None,
5072        };
5073        assert!(matches!(
5074            KekDecryptor::new([0_u8; 16]).resolve_key(
5075                crate::provider::default_provider(),
5076                DataEncryptionAlgorithm::Aes128Gcm,
5077                Some(&encrypted_key)
5078            ),
5079            Err(XmlEncError::KeyWrapIntegrity)
5080        ));
5081        assert!(matches!(
5082            KekDecryptor::new([0_u8; 32]).resolve_key(
5083                crate::provider::default_provider(),
5084                DataEncryptionAlgorithm::Aes128Gcm,
5085                Some(&encrypted_key)
5086            ),
5087            Err(XmlEncError::InvalidKekSize {
5088                algorithm: KeyWrapAlgorithm::AesKw128,
5089                expected: 16,
5090                actual: 32
5091            })
5092        ));
5093    }
5094
5095    #[test]
5096    fn decrypts_oaep11_with_independent_digest_and_mgf() {
5097        // XMLEnc 1.1 permits the OAEP digest and MGF1 digest to differ.
5098        let private_key = RsaPrivateKey::from_pkcs8_pem(include_str!(
5099            "../../tests/fixtures/keys/rsa/rsa-2048-key.pem"
5100        ))
5101        .expect("RSA donor private key must parse");
5102        let public_key = RsaPublicKey::from(&private_key);
5103        let session_key = [6_u8; 16];
5104        let label = b"xmlenc-label".to_vec();
5105        let wrapped = public_key
5106            .encrypt(
5107                &mut ChaCha20Rng::from_seed([17_u8; 32]),
5108                Oaep::<Sha256, Sha384>::new_with_mgf_hash_and_label(label.clone()),
5109                &session_key,
5110            )
5111            .expect("OAEP test wrapping must succeed");
5112        let encrypted_key = EncryptedKey {
5113            id: Some("wrapped-key".into()),
5114            sources: Default::default(),
5115            recipient: Some("recipient-a".into()),
5116            key_name: None,
5117            encryption_method: super::super::EncryptionMethod {
5118                chacha: None,
5119                algorithm: "http://www.w3.org/2009/xmlenc11#rsa-oaep".into(),
5120                key_size_bits: None,
5121                oaep_digest: Some("http://www.w3.org/2001/04/xmlenc#sha256".into()),
5122                mgf_algorithm: Some("http://www.w3.org/2009/xmlenc11#mgf1sha384".into()),
5123                oaep_params: Some(label),
5124            },
5125            cipher_data: super::super::CipherData::Value {
5126                value: STANDARD.encode(wrapped),
5127            },
5128            reference_list: None,
5129            carried_key_name: None,
5130        };
5131        let resolved = PrivateKeyDecryptor::new(private_key)
5132            .resolve_key(
5133                crate::provider::default_provider(),
5134                DataEncryptionAlgorithm::Aes128Gcm,
5135                Some(&encrypted_key),
5136            )
5137            .expect("OAEP 1.1 wrapped key must resolve");
5138        assert_eq!(resolved, session_key);
5139    }
5140
5141    #[test]
5142    fn decrypts_legacy_oaep_uri_with_sha256_digest() {
5143        // The legacy URI defaults MGF1 to SHA-1 when no xenc11:MGF child is present.
5144        let private_key = RsaPrivateKey::from_pkcs8_pem(include_str!(
5145            "../../tests/fixtures/keys/rsa/rsa-2048-key.pem"
5146        ))
5147        .expect("RSA donor private key must parse");
5148        let public_key = RsaPublicKey::from(&private_key);
5149        let session_key = [8_u8; 16];
5150        let wrapped = public_key
5151            .encrypt(
5152                &mut ChaCha20Rng::from_seed([19_u8; 32]),
5153                Oaep::<Sha256, Sha1>::new_with_mgf_hash(),
5154                &session_key,
5155            )
5156            .expect("legacy OAEP URI test wrapping must succeed");
5157        let encrypted_key = EncryptedKey {
5158            id: None,
5159            sources: Default::default(),
5160            recipient: None,
5161            key_name: None,
5162            encryption_method: super::super::EncryptionMethod {
5163                chacha: None,
5164                algorithm: "http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p".into(),
5165                key_size_bits: None,
5166                oaep_digest: Some("http://www.w3.org/2001/04/xmlenc#sha256".into()),
5167                mgf_algorithm: None,
5168                oaep_params: None,
5169            },
5170            cipher_data: super::super::CipherData::Value {
5171                value: STANDARD.encode(wrapped),
5172            },
5173            reference_list: None,
5174            carried_key_name: None,
5175        };
5176        let resolved = PrivateKeyDecryptor::new(private_key)
5177            .resolve_key(
5178                crate::provider::default_provider(),
5179                DataEncryptionAlgorithm::Aes128Gcm,
5180                Some(&encrypted_key),
5181            )
5182            .expect("legacy OAEP URI with SHA-256 must resolve");
5183        assert_eq!(resolved, session_key);
5184    }
5185
5186    #[test]
5187    fn decrypts_sha384_oaep_with_the_xmlenc_digest_uri() {
5188        // XML Encryption 1.1 reserves xmlenc#sha384 for SHA-384. Exercise both
5189        // OAEP algorithm URIs with their absent/explicit MGF1-SHA1 forms.
5190        let private_key = RsaPrivateKey::from_pkcs8_pem(include_str!(
5191            "../../tests/fixtures/keys/rsa/rsa-2048-key.pem"
5192        ))
5193        .expect("RSA donor private key must parse");
5194        let public_key = RsaPublicKey::from(&private_key);
5195        let session_key = [9_u8; 16];
5196        let digest = "http://www.w3.org/2001/04/xmlenc#sha384";
5197
5198        for (algorithm, mgf_algorithm) in [
5199            ("http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p", None),
5200            (
5201                "http://www.w3.org/2009/xmlenc11#rsa-oaep",
5202                Some("http://www.w3.org/2009/xmlenc11#mgf1sha1"),
5203            ),
5204        ] {
5205            let wrapped = public_key
5206                .encrypt(
5207                    &mut ChaCha20Rng::from_seed([23_u8; 32]),
5208                    Oaep::<Sha384, Sha1>::new_with_mgf_hash(),
5209                    &session_key,
5210                )
5211                .expect("SHA-384 OAEP test wrapping must succeed");
5212            let encrypted_key = EncryptedKey {
5213                id: None,
5214                sources: Default::default(),
5215                recipient: None,
5216                key_name: None,
5217                encryption_method: super::super::EncryptionMethod {
5218                    chacha: None,
5219                    algorithm: algorithm.into(),
5220                    key_size_bits: None,
5221                    oaep_digest: Some(digest.into()),
5222                    mgf_algorithm: mgf_algorithm.map(str::to_owned),
5223                    oaep_params: None,
5224                },
5225                cipher_data: super::super::CipherData::Value {
5226                    value: STANDARD.encode(wrapped),
5227                },
5228                reference_list: None,
5229                carried_key_name: None,
5230            };
5231            let resolved = PrivateKeyDecryptor::new(private_key.clone())
5232                .resolve_key(
5233                    crate::provider::default_provider(),
5234                    DataEncryptionAlgorithm::Aes128Gcm,
5235                    Some(&encrypted_key),
5236                )
5237                .expect("official XMLENC SHA-384 URI must resolve");
5238            assert_eq!(resolved, session_key);
5239        }
5240    }
5241
5242    #[test]
5243    fn rejects_unknown_oaep_digest_and_mgf_as_unsupported() {
5244        // Unknown algorithm URIs are declaration errors, not generic RSA failures.
5245        let private_key = RsaPrivateKey::from_pkcs8_pem(include_str!(
5246            "../../tests/fixtures/keys/rsa/rsa-2048-key.pem"
5247        ))
5248        .expect("RSA donor private key must parse");
5249        let decryptor = PrivateKeyDecryptor::new(private_key);
5250        let mut encrypted_key = EncryptedKey {
5251            sources: Default::default(),
5252            id: None,
5253            recipient: None,
5254            key_name: None,
5255            encryption_method: super::super::EncryptionMethod {
5256                chacha: None,
5257                algorithm: "http://www.w3.org/2009/xmlenc11#rsa-oaep".into(),
5258                key_size_bits: None,
5259                oaep_digest: Some("urn:unsupported:digest".into()),
5260                mgf_algorithm: Some("http://www.w3.org/2009/xmlenc11#mgf1sha1".into()),
5261                oaep_params: None,
5262            },
5263            cipher_data: super::super::CipherData::Value {
5264                value: STANDARD.encode([0_u8; 256]),
5265            },
5266            reference_list: None,
5267            carried_key_name: None,
5268        };
5269        assert!(matches!(
5270            decryptor.resolve_key(crate::provider::default_provider(), DataEncryptionAlgorithm::Aes128Gcm, Some(&encrypted_key)),
5271            Err(XmlEncError::UnsupportedAlgorithm(uri)) if uri == "urn:unsupported:digest"
5272        ));
5273
5274        encrypted_key.encryption_method.oaep_digest = None;
5275        encrypted_key.encryption_method.mgf_algorithm = Some("urn:unsupported:mgf".into());
5276        assert!(matches!(
5277            decryptor.resolve_key(crate::provider::default_provider(), DataEncryptionAlgorithm::Aes128Gcm, Some(&encrypted_key)),
5278            Err(XmlEncError::UnsupportedAlgorithm(uri)) if uri == "urn:unsupported:mgf"
5279        ));
5280    }
5281
5282    #[test]
5283    fn decryption_policy_enforces_oaep_digest_and_plaintext_limits() {
5284        // Algorithm and allocation policies are checked before key resolution
5285        // or plaintext materialization, including the document-declared MGF.
5286        let encrypted_key = EncryptedKey {
5287            id: None,
5288            recipient: Some("selected".into()),
5289            sources: Default::default(),
5290            key_name: None,
5291            encryption_method: super::super::EncryptionMethod {
5292                chacha: None,
5293                algorithm: KeyTransportAlgorithm::RsaOaepMgf1p.uri().into(),
5294                key_size_bits: None,
5295                oaep_digest: Some(OaepDigestAlgorithm::Sha256.uri().into()),
5296                mgf_algorithm: Some(
5297                    OaepDigestAlgorithm::Sha384
5298                        .mgf_uri()
5299                        .expect("MGF1-SHA384 URI")
5300                        .into(),
5301                ),
5302                oaep_params: None,
5303            },
5304            cipher_data: super::super::CipherData::Value {
5305                value: STANDARD.encode([0_u8; 256]),
5306            },
5307            reference_list: None,
5308            carried_key_name: None,
5309        };
5310        let encrypted = EncryptedData {
5311            encapsulation_methods: Vec::new(),
5312            id: None,
5313            derived_keys: Vec::new(),
5314            agreement_methods: Vec::new(),
5315            encrypted_type: None,
5316            key_name: None,
5317            encryption_method: super::super::EncryptionMethod {
5318                chacha: None,
5319                algorithm: DataEncryptionAlgorithm::Aes128Gcm.uri().into(),
5320                key_size_bits: None,
5321                oaep_digest: None,
5322                mgf_algorithm: None,
5323                oaep_params: None,
5324            },
5325            encrypted_keys: vec![encrypted_key],
5326            cipher_data: super::super::CipherData::Value {
5327                value: STANDARD.encode([0_u8; 28]),
5328            },
5329        };
5330        let policy = crate::policy::DecryptionPolicy {
5331            oaep_digests: Some(std::collections::HashSet::from([
5332                OaepDigestAlgorithm::Sha256,
5333            ])),
5334            ..crate::policy::DecryptionPolicy::default()
5335        };
5336        assert!(matches!(
5337            DecryptContext::new(&RecipientKeyResolver {
5338                recipient: "selected",
5339                key: vec![0_u8; 16],
5340            })
5341            .policy(policy)
5342            .decrypt_data(&encrypted),
5343            Err(XmlEncError::Policy(
5344                crate::policy::PolicyViolation::Algorithm { algorithm, .. }
5345            )) if algorithm == OaepDigestAlgorithm::Sha384.mgf_uri().expect("MGF1-SHA384 URI")
5346        ));
5347
5348        let ciphertext = crate::provider::default_provider()
5349            .encrypt_data(DataEncryptionAlgorithm::Aes128Gcm, &[0_u8; 16], b"four")
5350            .expect("test encryption must succeed");
5351        let bounded = EncryptedData {
5352            encrypted_keys: Vec::new(),
5353            cipher_data: super::super::CipherData::Value {
5354                value: STANDARD.encode(ciphertext),
5355            },
5356            ..encrypted
5357        };
5358        let policy = crate::policy::DecryptionPolicy {
5359            resources: crate::policy::ResourcePolicy {
5360                max_encryption_plaintext_bytes: 3,
5361                ..crate::policy::ResourcePolicy::default()
5362            },
5363            ..crate::policy::DecryptionPolicy::default()
5364        };
5365        assert!(matches!(
5366            DecryptContext::new(&SymmetricKeyDecryptor::new([0_u8; 16]))
5367                .policy(policy)
5368                .decrypt_data(&bounded),
5369            Err(XmlEncError::Policy(
5370                crate::policy::PolicyViolation::ResourceLimit {
5371                    resource: crate::policy::resource_name::ENCRYPTION_PLAINTEXT_BYTES,
5372                    maximum: 3,
5373                    actual: 4
5374                }
5375            ))
5376        ));
5377
5378        let cbc_ciphertext = crate::provider::default_provider()
5379            .encrypt_data(DataEncryptionAlgorithm::Aes128Cbc, &[0_u8; 16], b"four")
5380            .expect("test CBC encryption must succeed");
5381        let bounded_cbc = EncryptedData {
5382            encryption_method: super::super::EncryptionMethod {
5383                chacha: None,
5384                algorithm: DataEncryptionAlgorithm::Aes128Cbc.uri().into(),
5385                key_size_bits: None,
5386                oaep_digest: None,
5387                mgf_algorithm: None,
5388                oaep_params: None,
5389            },
5390            encrypted_keys: Vec::new(),
5391            cipher_data: super::super::CipherData::Value {
5392                value: STANDARD.encode(cbc_ciphertext),
5393            },
5394            ..bounded
5395        };
5396        let policy = crate::policy::DecryptionPolicy {
5397            resources: crate::policy::ResourcePolicy {
5398                max_encryption_plaintext_bytes: 4,
5399                ..crate::policy::ResourcePolicy::default()
5400            },
5401            ..crate::policy::DecryptionPolicy::default()
5402        };
5403        assert_eq!(
5404            DecryptContext::new(&SymmetricKeyDecryptor::new([0_u8; 16]))
5405                .policy(policy)
5406                .decrypt_data(&bounded_cbc)
5407                .expect("CBC plaintext at the configured limit must decrypt"),
5408            DecryptedContent::Bytes(b"four".to_vec())
5409        );
5410    }
5411
5412    #[test]
5413    fn typed_decryption_input_cannot_bypass_metadata_policy() {
5414        // Callers may construct EncryptedData directly instead of using the XML
5415        // parser, so the operation boundary must enforce the same metadata cap.
5416        let ciphertext = crate::provider::default_provider()
5417            .encrypt_data(DataEncryptionAlgorithm::Aes128Gcm, &[0_u8; 16], b"data")
5418            .expect("test encryption must succeed");
5419        let encrypted = EncryptedData {
5420            encapsulation_methods: Vec::new(),
5421            id: Some("oversized".into()),
5422            derived_keys: Vec::new(),
5423            agreement_methods: Vec::new(),
5424            encrypted_type: None,
5425            key_name: None,
5426            encryption_method: super::super::EncryptionMethod {
5427                chacha: None,
5428                algorithm: DataEncryptionAlgorithm::Aes128Gcm.uri().into(),
5429                key_size_bits: None,
5430                oaep_digest: None,
5431                mgf_algorithm: None,
5432                oaep_params: None,
5433            },
5434            encrypted_keys: Vec::new(),
5435            cipher_data: super::super::CipherData::Value {
5436                value: STANDARD.encode(ciphertext),
5437            },
5438        };
5439        let policy = crate::policy::DecryptionPolicy {
5440            resources: crate::policy::ResourcePolicy {
5441                max_encryption_metadata_bytes: 8,
5442                ..crate::policy::ResourcePolicy::default()
5443            },
5444            ..crate::policy::DecryptionPolicy::default()
5445        };
5446
5447        let resolver = AllCallsResolver {
5448            calls: Cell::new(0),
5449            key: vec![0_u8; 16],
5450        };
5451        assert!(matches!(
5452            DecryptContext::new(&resolver)
5453                .policy(policy)
5454                .decrypt_data(&encrypted),
5455            Err(XmlEncError::Policy(
5456                crate::policy::PolicyViolation::ResourceLimit {
5457                    resource: crate::policy::resource_name::ENCRYPTION_METADATA_BYTES,
5458                    maximum: 8,
5459                    actual: 9,
5460                }
5461            ))
5462        ));
5463        assert_eq!(
5464            resolver.calls.get(),
5465            0,
5466            "metadata preflight must gate resolver-controlled work"
5467        );
5468    }
5469
5470    #[test]
5471    fn typed_cipher_values_are_bounded_before_decode_or_resolution() {
5472        // Public typed input bypasses the XML parser, so the decryption boundary
5473        // must re-establish both content and recipient CipherValue size invariants.
5474        let key = [0x41_u8; 16];
5475        let ciphertext = crate::provider::default_provider()
5476            .encrypt_data(DataEncryptionAlgorithm::Aes128Gcm, &key, b"data")
5477            .expect("test encryption must succeed");
5478        let mut encrypted = EncryptedData {
5479            encapsulation_methods: Vec::new(),
5480            id: None,
5481            derived_keys: Vec::new(),
5482            agreement_methods: Vec::new(),
5483            encrypted_type: None,
5484            key_name: None,
5485            encryption_method: super::super::EncryptionMethod {
5486                chacha: None,
5487                algorithm: DataEncryptionAlgorithm::Aes128Gcm.uri().into(),
5488                key_size_bits: None,
5489                oaep_digest: None,
5490                mgf_algorithm: None,
5491                oaep_params: None,
5492            },
5493            encrypted_keys: Vec::new(),
5494            cipher_data: super::super::CipherData::Value {
5495                value: STANDARD.encode(ciphertext),
5496            },
5497        };
5498        let policy = crate::policy::DecryptionPolicy {
5499            resources: crate::policy::ResourcePolicy {
5500                max_encryption_plaintext_bytes: 4,
5501                ..crate::policy::ResourcePolicy::default()
5502            },
5503            ..crate::policy::DecryptionPolicy::default()
5504        };
5505        *encrypted
5506            .cipher_data
5507            .inline_value_mut()
5508            .expect("inline ciphertext") = "A".repeat(48);
5509        assert!(matches!(
5510            DecryptContext::new(&SymmetricKeyDecryptor::new(key))
5511                .policy(policy)
5512                .decrypt_data(&encrypted),
5513            Err(XmlEncError::Policy(
5514                crate::policy::PolicyViolation::ResourceLimit {
5515                    resource: crate::policy::resource_name::ENCRYPTION_PLAINTEXT_BYTES,
5516                    ..
5517                }
5518            ))
5519        ));
5520
5521        *encrypted
5522            .cipher_data
5523            .inline_value_mut()
5524            .expect("inline ciphertext") = STANDARD.encode([0_u8; 28]);
5525        encrypted.encrypted_keys.push(EncryptedKey {
5526            sources: Default::default(),
5527            id: None,
5528            recipient: None,
5529            key_name: None,
5530            encryption_method: super::super::EncryptionMethod {
5531                chacha: None,
5532                algorithm: KeyWrapAlgorithm::AesKw128.uri().into(),
5533                key_size_bits: None,
5534                oaep_digest: None,
5535                mgf_algorithm: None,
5536                oaep_params: None,
5537            },
5538            cipher_data: super::super::CipherData::Value {
5539                value: "A".repeat(MAX_CIPHER_VALUE_BASE64_LEN + 4),
5540            },
5541            reference_list: None,
5542            carried_key_name: None,
5543        });
5544        let resolver = CountingResolver {
5545            candidate_calls: Cell::new(0),
5546            key: key.to_vec(),
5547        };
5548        assert!(matches!(
5549            DecryptContext::new(&resolver).decrypt_data(&encrypted),
5550            Err(XmlEncError::InvalidStructure(_))
5551        ));
5552        assert_eq!(resolver.candidate_calls.get(), 0);
5553
5554        *encrypted.encrypted_keys[0]
5555            .cipher_data
5556            .inline_value_mut()
5557            .expect("inline wrapped key") = "AAAA".into();
5558        let aggregate_encoded_len = encrypted
5559            .cipher_data
5560            .inline_value()
5561            .expect("inline ciphertext")
5562            .len()
5563            + encrypted.encrypted_keys[0]
5564                .cipher_data
5565                .inline_value()
5566                .expect("inline wrapped key")
5567                .len();
5568        let policy = crate::policy::DecryptionPolicy {
5569            resources: crate::policy::ResourcePolicy {
5570                max_encryption_plaintext_bytes: 4,
5571                max_xml_document_bytes: aggregate_encoded_len - 1,
5572                ..crate::policy::ResourcePolicy::default()
5573            },
5574            ..crate::policy::DecryptionPolicy::default()
5575        };
5576        let resolver = CountingResolver {
5577            candidate_calls: Cell::new(0),
5578            key: key.to_vec(),
5579        };
5580        assert!(matches!(
5581            DecryptContext::new(&resolver)
5582                .policy(policy)
5583                .decrypt_data(&encrypted),
5584            Err(XmlEncError::Policy(
5585                crate::policy::PolicyViolation::ResourceLimit {
5586                    resource:
5587                        crate::policy::resource_name::AGGREGATE_ENCRYPTION_CIPHER_VALUE_BYTES,
5588                    maximum,
5589                    actual,
5590                }
5591            )) if maximum == aggregate_encoded_len - 1 && actual == aggregate_encoded_len
5592        ));
5593        assert_eq!(resolver.candidate_calls.get(), 0);
5594    }
5595
5596    #[cfg(feature = "experimental-pq")]
5597    #[test]
5598    fn typed_kem_width_is_checked_before_any_resolver_dispatch() {
5599        // Exercise the existing facade preflight with a permissive custom
5600        // resolver, including nested KEKs, rather than built-in KEM recovery.
5601        let document = crate::XmlDomDocument::parse(include_str!(
5602            "../../tests/fixtures/xmlenc/aleksey-xmlenc-01/enc-aes128gcm-em-ml-kem-512.xml"
5603        ))
5604        .expect("valid donor XML");
5605        let node = document
5606            .descendants()
5607            .find(|node| node.has_tag_name((crate::xmlenc::types::XMLENC_NS, "EncryptedData")))
5608            .expect("donor EncryptedData");
5609        let mut policy = crate::policy::DecryptionPolicy::default();
5610        policy
5611            .key_establishment
5612            .encapsulation_algorithms
5613            .insert(crate::provider::KeyEncapsulationAlgorithm::MlKem512);
5614        let data = super::super::parse_encrypted_data_node_with_policy(node, &policy)
5615            .expect("valid donor encryption metadata");
5616        for width in [0, 767, 769] {
5617            for nested in [false, true] {
5618                let mut encrypted = data.clone();
5619                encrypted.encapsulation_methods[0]
5620                    .ciphertext
5621                    .resize(width, 0);
5622                if nested {
5623                    encrypted.encrypted_keys.push(EncryptedKey {
5624                        sources: super::super::EncryptionKeySources {
5625                            encapsulation_methods: std::mem::take(
5626                                &mut encrypted.encapsulation_methods,
5627                            ),
5628                            ..Default::default()
5629                        },
5630                        id: None,
5631                        recipient: None,
5632                        key_name: None,
5633                        encryption_method: super::super::EncryptionMethod {
5634                            chacha: None,
5635                            algorithm: KeyWrapAlgorithm::AesKw128.uri().into(),
5636                            key_size_bits: None,
5637                            oaep_digest: None,
5638                            mgf_algorithm: None,
5639                            oaep_params: None,
5640                        },
5641                        cipher_data: super::super::CipherData::Bytes(vec![0; 24]),
5642                        reference_list: None,
5643                        carried_key_name: None,
5644                    });
5645                }
5646                let resolver = CountingResolver {
5647                    candidate_calls: Cell::new(0),
5648                    key: vec![0; 16],
5649                };
5650                let error = DecryptContext::new(&resolver)
5651                    .policy(policy.clone())
5652                    .decrypt_data(&encrypted)
5653                    .expect_err("malformed typed KEM width rejected");
5654                assert!(
5655                    error
5656                        .to_string()
5657                        .contains("ciphertext size does not match Algorithm"),
5658                    "{error}"
5659                );
5660                assert_eq!(resolver.candidate_calls.get(), 0);
5661            }
5662        }
5663        // A custom resolver cannot bypass the facade's default KEM/CBC gate.
5664        let mut encrypted = data;
5665        encrypted.encryption_method.algorithm = DataEncryptionAlgorithm::Aes256Cbc.uri().into();
5666        encrypted.cipher_data = super::super::CipherData::Bytes(vec![0; 32]);
5667        let resolver = CountingResolver {
5668            candidate_calls: Cell::new(0),
5669            key: vec![0; 32],
5670        };
5671        assert!(matches!(
5672            DecryptContext::new(&resolver)
5673                .policy(policy)
5674                .decrypt_data(&encrypted),
5675            Err(XmlEncError::Policy(
5676                crate::policy::PolicyViolation::Algorithm {
5677                    operation: "direct KEM content without external authentication",
5678                    ..
5679                }
5680            ))
5681        ));
5682        assert_eq!(resolver.candidate_calls.get(), 0);
5683    }
5684
5685    #[cfg(feature = "experimental-pq")]
5686    #[test]
5687    fn typed_kem_ciphertext_shares_aggregate_cipher_value_budget() {
5688        // Typed input must charge the same base64-projected KEM ciphertext as
5689        // XML input, including descriptors nested under an EncryptedKey.
5690        let xml = include_str!(
5691            "../../tests/fixtures/xmlenc/aleksey-xmlenc-01/enc-aes128gcm-em-ml-kem-512.xml"
5692        );
5693        let document = crate::XmlDomDocument::parse(xml).expect("donor XML parses");
5694        let node = document
5695            .descendants()
5696            .find(|node| node.has_tag_name((crate::xmlenc::types::XMLENC_NS, "EncryptedData")))
5697            .expect("donor EncryptedData");
5698        let mut policy = crate::policy::DecryptionPolicy::default();
5699        policy
5700            .key_establishment
5701            .encapsulation_algorithms
5702            .insert(crate::provider::KeyEncapsulationAlgorithm::MlKem512);
5703        let mut encrypted = super::super::parse_encrypted_data_node_with_policy(node, &policy)
5704            .expect("permitted donor recipient");
5705        let content = cipher_data_storage_len(&encrypted.cipher_data);
5706        let kem_encoded = 4 * encrypted.encapsulation_methods[0]
5707            .ciphertext
5708            .len()
5709            .div_ceil(3);
5710        assert!(
5711            validate_typed_cipher_values(
5712                &encrypted,
5713                DataEncryptionAlgorithm::Aes128Gcm,
5714                usize::MAX / 2,
5715                content + kem_encoded
5716            )
5717            .is_ok()
5718        );
5719        for nested in [false, true] {
5720            if nested {
5721                encrypted.encrypted_keys.push(EncryptedKey {
5722                    sources: super::super::EncryptionKeySources {
5723                        encapsulation_methods: std::mem::take(&mut encrypted.encapsulation_methods),
5724                        ..Default::default()
5725                    },
5726                    id: None,
5727                    recipient: None,
5728                    key_name: None,
5729                    encryption_method: super::super::EncryptionMethod {
5730                        chacha: None,
5731                        algorithm: KeyWrapAlgorithm::AesKw128.uri().into(),
5732                        key_size_bits: None,
5733                        oaep_digest: None,
5734                        mgf_algorithm: None,
5735                        oaep_params: None,
5736                    },
5737                    cipher_data: super::super::CipherData::Bytes(vec![0; 24]),
5738                    reference_list: None,
5739                    carried_key_name: None,
5740                });
5741            }
5742            let total = content + kem_encoded + if nested { 24 } else { 0 };
5743            policy.resources.max_xml_document_bytes = total - 1;
5744            let resolver = CountingResolver {
5745                candidate_calls: Cell::new(0),
5746                key: vec![0; 16],
5747            };
5748            assert!(
5749                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)
5750            );
5751            assert_eq!(resolver.candidate_calls.get(), 0);
5752        }
5753    }
5754
5755    #[test]
5756    fn typed_legacy_oaep_accepts_explicit_mgf() {
5757        // The legacy URI defaults to MGF1-SHA1 when MGF is absent, but
5758        // libxmlsec1 also accepts the XMLEnc 1.1 child explicitly.
5759        let key = [0x43_u8; 16];
5760        let ciphertext = crate::provider::default_provider()
5761            .encrypt_data(DataEncryptionAlgorithm::Aes128Gcm, &key, b"data")
5762            .expect("test encryption must succeed");
5763        let encrypted = EncryptedData {
5764            encapsulation_methods: Vec::new(),
5765            id: None,
5766            derived_keys: Vec::new(),
5767            agreement_methods: Vec::new(),
5768            encrypted_type: None,
5769            key_name: None,
5770            encryption_method: super::super::EncryptionMethod {
5771                chacha: None,
5772                algorithm: DataEncryptionAlgorithm::Aes128Gcm.uri().into(),
5773                key_size_bits: None,
5774                oaep_digest: None,
5775                mgf_algorithm: None,
5776                oaep_params: None,
5777            },
5778            encrypted_keys: vec![EncryptedKey {
5779                sources: Default::default(),
5780                id: None,
5781                recipient: None,
5782                key_name: None,
5783                encryption_method: super::super::EncryptionMethod {
5784                    chacha: None,
5785                    algorithm: KeyTransportAlgorithm::RsaOaepMgf1p.uri().into(),
5786                    key_size_bits: None,
5787                    oaep_digest: Some(OaepDigestAlgorithm::Sha256.uri().into()),
5788                    mgf_algorithm: Some(
5789                        OaepDigestAlgorithm::Sha384
5790                            .mgf_uri()
5791                            .expect("MGF1-SHA384 URI")
5792                            .into(),
5793                    ),
5794                    oaep_params: None,
5795                },
5796                cipher_data: super::super::CipherData::Value {
5797                    value: STANDARD.encode([0_u8; 256]),
5798                },
5799                reference_list: None,
5800                carried_key_name: None,
5801            }],
5802            cipher_data: super::super::CipherData::Value {
5803                value: STANDARD.encode(ciphertext),
5804            },
5805        };
5806        let resolver = CountingResolver {
5807            candidate_calls: Cell::new(0),
5808            key: key.to_vec(),
5809        };
5810
5811        assert_eq!(
5812            DecryptContext::new(&resolver)
5813                .decrypt_data(&encrypted)
5814                .expect("explicit legacy OAEP MGF must reach key resolution"),
5815            DecryptedContent::Bytes(b"data".to_vec())
5816        );
5817        assert_eq!(resolver.candidate_calls.get(), 1);
5818    }
5819
5820    #[test]
5821    fn typed_zero_key_size_is_rejected_before_key_resolution() {
5822        // Parsed KeySize values are positive. Caller-constructed values must
5823        // preserve the same invariant for algorithms without a fixed AES width.
5824        let key = [0x45_u8; 16];
5825        let ciphertext = crate::provider::default_provider()
5826            .encrypt_data(DataEncryptionAlgorithm::Aes128Gcm, &key, b"data")
5827            .expect("test encryption must succeed");
5828        let encrypted = EncryptedData {
5829            encapsulation_methods: Vec::new(),
5830            id: None,
5831            derived_keys: Vec::new(),
5832            agreement_methods: Vec::new(),
5833            encrypted_type: None,
5834            key_name: None,
5835            encryption_method: super::super::EncryptionMethod {
5836                chacha: None,
5837                algorithm: DataEncryptionAlgorithm::Aes128Gcm.uri().into(),
5838                key_size_bits: None,
5839                oaep_digest: None,
5840                mgf_algorithm: None,
5841                oaep_params: None,
5842            },
5843            encrypted_keys: vec![EncryptedKey {
5844                id: None,
5845                sources: Default::default(),
5846                recipient: None,
5847                key_name: None,
5848                encryption_method: super::super::EncryptionMethod {
5849                    chacha: None,
5850                    algorithm: KeyTransportAlgorithm::RsaOaep11.uri().into(),
5851                    key_size_bits: Some(0),
5852                    oaep_digest: Some(OaepDigestAlgorithm::Sha256.uri().into()),
5853                    mgf_algorithm: Some(
5854                        OaepDigestAlgorithm::Sha256
5855                            .mgf_uri()
5856                            .expect("MGF1-SHA256 URI")
5857                            .into(),
5858                    ),
5859                    oaep_params: None,
5860                },
5861                cipher_data: super::super::CipherData::Value {
5862                    value: STANDARD.encode([0_u8; 256]),
5863                },
5864                reference_list: None,
5865                carried_key_name: None,
5866            }],
5867            cipher_data: super::super::CipherData::Value {
5868                value: STANDARD.encode(ciphertext),
5869            },
5870        };
5871        let resolver = CountingResolver {
5872            candidate_calls: Cell::new(0),
5873            key: key.to_vec(),
5874        };
5875
5876        assert!(matches!(
5877            DecryptContext::new(&resolver).decrypt_data(&encrypted),
5878            Err(XmlEncError::InvalidStructure(message))
5879                if message == "KeySize must be a positive integer"
5880        ));
5881        assert_eq!(resolver.candidate_calls.get(), 0);
5882    }
5883
5884    #[test]
5885    fn typed_content_method_is_validated_before_key_resolution() {
5886        // Caller-constructed values bypass XML parsing, so a fixed-size AES
5887        // KeySize mismatch must fail at the operation boundary.
5888        let key = [0x44_u8; 16];
5889        let ciphertext = crate::provider::default_provider()
5890            .encrypt_data(DataEncryptionAlgorithm::Aes128Gcm, &key, b"data")
5891            .expect("test encryption must succeed");
5892        let encrypted = EncryptedData {
5893            encapsulation_methods: Vec::new(),
5894            id: None,
5895            derived_keys: Vec::new(),
5896            agreement_methods: Vec::new(),
5897            encrypted_type: None,
5898            key_name: None,
5899            encryption_method: super::super::EncryptionMethod {
5900                chacha: None,
5901                algorithm: DataEncryptionAlgorithm::Aes128Gcm.uri().into(),
5902                key_size_bits: Some(256),
5903                oaep_digest: None,
5904                mgf_algorithm: None,
5905                oaep_params: None,
5906            },
5907            encrypted_keys: Vec::new(),
5908            cipher_data: super::super::CipherData::Value {
5909                value: STANDARD.encode(ciphertext),
5910            },
5911        };
5912        let resolver = AllCallsResolver {
5913            calls: Cell::new(0),
5914            key: key.to_vec(),
5915        };
5916
5917        assert!(matches!(
5918            DecryptContext::new(&resolver).decrypt_data(&encrypted),
5919            Err(XmlEncError::InvalidStructure(message))
5920                if message.contains("requires KeySize 128, got 256")
5921        ));
5922        assert_eq!(resolver.calls.get(), 0);
5923    }
5924
5925    #[test]
5926    fn unknown_encrypted_key_algorithm_never_reaches_resolver() {
5927        // Extension URIs cannot bypass transport/wrap allowlists by relying on
5928        // an application resolver that happens to return usable key bytes.
5929        let key = [0x42_u8; 16];
5930        let ciphertext = crate::provider::default_provider()
5931            .encrypt_data(DataEncryptionAlgorithm::Aes128Gcm, &key, b"data")
5932            .expect("test encryption must succeed");
5933        let encrypted = EncryptedData {
5934            encapsulation_methods: Vec::new(),
5935            id: None,
5936            derived_keys: Vec::new(),
5937            agreement_methods: Vec::new(),
5938            encrypted_type: None,
5939            key_name: None,
5940            encryption_method: super::super::EncryptionMethod {
5941                chacha: None,
5942                algorithm: DataEncryptionAlgorithm::Aes128Gcm.uri().into(),
5943                key_size_bits: None,
5944                oaep_digest: None,
5945                mgf_algorithm: None,
5946                oaep_params: None,
5947            },
5948            encrypted_keys: vec![EncryptedKey {
5949                id: None,
5950                recipient: None,
5951                sources: Default::default(),
5952                key_name: None,
5953                encryption_method: super::super::EncryptionMethod {
5954                    chacha: None,
5955                    algorithm: "urn:example:unknown-key-algorithm".into(),
5956                    key_size_bits: None,
5957                    oaep_digest: None,
5958                    mgf_algorithm: None,
5959                    oaep_params: None,
5960                },
5961                cipher_data: super::super::CipherData::Value {
5962                    value: STANDARD.encode([0_u8; 24]),
5963                },
5964                reference_list: None,
5965                carried_key_name: None,
5966            }],
5967            cipher_data: super::super::CipherData::Value {
5968                value: STANDARD.encode(ciphertext),
5969            },
5970        };
5971        let resolver = CountingResolver {
5972            candidate_calls: Cell::new(0),
5973            key: key.to_vec(),
5974        };
5975
5976        assert!(matches!(
5977            DecryptContext::new(&resolver).decrypt_data(&encrypted),
5978            Err(XmlEncError::UnsupportedAlgorithm(_))
5979        ));
5980        assert_eq!(resolver.candidate_calls.get(), 0);
5981    }
5982
5983    #[test]
5984    fn cbc_padding_errors_do_not_expose_decrypted_octets() {
5985        // The error contract hides padding details, but callers still need an
5986        // authenticated envelope or a policy that rejects unauthenticated CBC.
5987        let error = map_data_decryption_error(
5988            DataEncryptionAlgorithm::Aes128Cbc,
5989            32,
5990            crate::provider::ProviderError::InvalidInput(
5991                crate::provider::ProviderInputError::AesCbcCiphertext,
5992            ),
5993        );
5994
5995        assert_eq!(error.to_string(), "invalid XMLEnc padding");
5996    }
5997
5998    #[test]
5999    fn replaces_element_and_content_in_caller_owned_documents() {
6000        // Element plaintext replaces the encrypted node itself, while Content
6001        // plaintext becomes children of the existing parent element.
6002        let key = [0x31_u8; 16];
6003        let element = encrypted_gcm_element(
6004            "http://www.w3.org/2001/04/xmlenc#Element",
6005            "<secret id=\"visible\">value</secret>",
6006            None,
6007            true,
6008            &key,
6009        );
6010        assert_eq!(
6011            decrypt_document(&element, None, &SymmetricKeyDecryptor::new(key))
6012                .expect("root Element replacement must succeed"),
6013            "<secret id=\"visible\">value</secret>"
6014        );
6015
6016        let content = encrypted_gcm_element(
6017            "http://www.w3.org/2001/04/xmlenc#Content",
6018            "before<child/>after",
6019            None,
6020            false,
6021            &key,
6022        );
6023        let document =
6024            format!("<root xmlns:xenc=\"{XMLENC_NS}\"><prefix/>{content}<suffix/></root>");
6025        let replaced = decrypt_document(&document, None, &SymmetricKeyDecryptor::new(key))
6026            .expect("nested Content replacement must succeed");
6027        assert_eq!(
6028            replaced,
6029            format!(
6030                "<root xmlns:xenc=\"{XMLENC_NS}\"><prefix/>before<child/>after<suffix/></root>"
6031            )
6032        );
6033    }
6034
6035    #[test]
6036    fn accepts_whitespace_and_comments_around_element_plaintext() {
6037        // Element serialization may carry harmless boundary whitespace/comments;
6038        // they must be preserved while the fragment still contains one element.
6039        let key = [0x34_u8; 16];
6040        let plaintext = "\n<!--before--><secret/><!--after-->\n";
6041        let encrypted = encrypted_gcm_element(
6042            "http://www.w3.org/2001/04/xmlenc#Element",
6043            plaintext,
6044            None,
6045            true,
6046            &key,
6047        );
6048
6049        assert_eq!(
6050            decrypt_document(&encrypted, None, &SymmetricKeyDecryptor::new(key))
6051                .expect("one element with boundary trivia must be accepted"),
6052            plaintext
6053        );
6054    }
6055
6056    #[test]
6057    fn decrypts_unknown_and_empty_type_hints_as_opaque_bytes() {
6058        // Type is an application hint, not an algorithm constraint. Unknown and
6059        // empty values must not prevent decryption of otherwise valid binary data.
6060        let key = [0x35_u8; 16];
6061        let plaintext = "\0opaque\u{ff}bytes";
6062        let unknown = encrypted_gcm_element("urn:example:binary", plaintext, None, true, &key);
6063        let empty = encrypted_gcm_element("", plaintext, None, true, &key).replacen(
6064            "<xenc:EncryptedData",
6065            "<xenc:EncryptedData Type=\"\"",
6066            1,
6067        );
6068
6069        let parsed = parse_encrypted_data(&unknown).expect("unknown Type must remain parseable");
6070        assert_eq!(
6071            parsed.encrypted_type,
6072            Some(EncryptedDataType::Other("urn:example:binary".into()))
6073        );
6074        assert!(matches!(
6075            decrypt_document(&unknown, None, &SymmetricKeyDecryptor::new(key)),
6076            Err(XmlEncError::ReplacementRequiresXml)
6077        ));
6078
6079        for encrypted in [unknown, empty] {
6080            assert_eq!(
6081                decrypt(&encrypted, &SymmetricKeyDecryptor::new(key))
6082                    .expect("opaque Type hints must not block decryption"),
6083                DecryptedContent::Bytes(plaintext.as_bytes().to_vec())
6084            );
6085        }
6086    }
6087
6088    #[test]
6089    fn selects_document_encrypted_data_by_id_and_rejects_ambiguity() {
6090        // Selection must never decrypt an arbitrary first match when a document
6091        // contains multiple encrypted regions.
6092        let key = [0x32_u8; 16];
6093        let first = encrypted_gcm_element(
6094            "http://www.w3.org/2001/04/xmlenc#Content",
6095            "first",
6096            Some("first"),
6097            false,
6098            &key,
6099        );
6100        let second = encrypted_gcm_element(
6101            "http://www.w3.org/2001/04/xmlenc#Content",
6102            "second",
6103            Some("second"),
6104            false,
6105            &key,
6106        );
6107        let document = format!("<root xmlns:xenc=\"{XMLENC_NS}\">{first}{second}</root>");
6108        let resolver = SymmetricKeyDecryptor::new(key);
6109        assert!(matches!(
6110            decrypt_document(&document, None, &resolver),
6111            Err(XmlEncError::AmbiguousEncryptedData)
6112        ));
6113        let replaced = decrypt_document(&document, Some("second"), &resolver)
6114            .expect("Id selection must choose exactly one encrypted region");
6115        assert!(replaced.contains("second"));
6116        assert!(replaced.contains("Id=\"first\""));
6117        assert!(matches!(
6118            decrypt_document(&document, Some("missing"), &resolver),
6119            Err(XmlEncError::EncryptedDataNotFound)
6120        ));
6121    }
6122
6123    #[test]
6124    fn selects_encrypted_data_below_a_unique_operation_start_node() {
6125        // CLI-compatible selection starts at an arbitrary ID-bearing ancestor;
6126        // missing/duplicate IDs and multiple encrypted descendants fail closed.
6127        let key = [0x42_u8; 16];
6128        let first = encrypted_gcm_element(
6129            "http://www.w3.org/2001/04/xmlenc#Content",
6130            "first",
6131            None,
6132            false,
6133            &key,
6134        );
6135        let second = encrypted_gcm_element(
6136            "http://www.w3.org/2001/04/xmlenc#Content",
6137            "second",
6138            None,
6139            false,
6140            &key,
6141        );
6142        let document = format!(
6143            "<root xmlns:xenc=\"{XMLENC_NS}\"><scope Id=\"first\">{first}</scope><scope Id=\"second\">{second}</scope></root>"
6144        );
6145        let resolver = SymmetricKeyDecryptor::new(key);
6146        let context = DecryptContext::new(&resolver);
6147        let replaced = context
6148            .decrypt_document_from_start_node(&document, Some("second"))
6149            .expect("ancestor ID must select its encrypted descendant");
6150        assert!(replaced.contains("<scope Id=\"second\">second</scope>"));
6151        assert!(replaced.contains("<scope Id=\"first\"><xenc:EncryptedData"));
6152
6153        assert!(matches!(
6154            context.decrypt_document_from_start_node(&document, Some("missing")),
6155            Err(XmlEncError::SelectedNodeUnavailable { id }) if id == "missing"
6156        ));
6157        let duplicate = document.replace("Id=\"second\"", "Id=\"first\"");
6158        assert!(matches!(
6159            context.decrypt_document_from_start_node(&duplicate, Some("first")),
6160            Err(XmlEncError::SelectedNodeUnavailable { id }) if id == "first"
6161        ));
6162        let ambiguous = format!(
6163            "<root xmlns:xenc=\"{XMLENC_NS}\"><scope Id=\"selected\">{first}{second}</scope></root>"
6164        );
6165        assert!(matches!(
6166            context.decrypt_document_from_start_node(&ambiguous, Some("selected")),
6167            Err(XmlEncError::AmbiguousEncryptedData)
6168        ));
6169
6170        let first_replaced = context
6171            .decrypt_first_document_from_start_node(&ambiguous, Some("selected"))
6172            .expect("first-match selection must leave later encrypted descendants untouched");
6173        assert!(first_replaced.contains("<scope Id=\"selected\">first<xenc:EncryptedData"));
6174        let replaced_document =
6175            Document::parse(&first_replaced).expect("first-match output must remain valid XML");
6176        assert_eq!(
6177            replaced_document
6178                .descendants()
6179                .filter(|node| node.has_tag_name((XMLENC_NS, "EncryptedData")))
6180                .count(),
6181            1
6182        );
6183    }
6184
6185    #[test]
6186    fn rejects_non_xml_or_malformed_document_replacement_plaintext() {
6187        // The document API must not expose binary content or return a document
6188        // made malformed by unauthenticated structure assumptions.
6189        let key = [0x33_u8; 16];
6190        let binary = encrypted_gcm_element("", "binary", None, true, &key);
6191        assert!(matches!(
6192            decrypt_document(&binary, None, &SymmetricKeyDecryptor::new(key)),
6193            Err(XmlEncError::ReplacementRequiresXml)
6194        ));
6195
6196        let malformed = encrypted_gcm_element(
6197            "http://www.w3.org/2001/04/xmlenc#Element",
6198            "<unclosed>",
6199            None,
6200            true,
6201            &key,
6202        );
6203        assert!(matches!(
6204            decrypt_document(&malformed, None, &SymmetricKeyDecryptor::new(key)),
6205            Err(XmlEncError::XmlParse(_))
6206        ));
6207
6208        for invalid_element in ["text-only", "<first/><second/>"] {
6209            let encrypted = encrypted_gcm_element(
6210                "http://www.w3.org/2001/04/xmlenc#Element",
6211                invalid_element,
6212                None,
6213                false,
6214                &key,
6215            );
6216            let document = format!("<root xmlns:xenc=\"{XMLENC_NS}\">{encrypted}</root>");
6217            assert!(
6218                decrypt_document(&document, None, &SymmetricKeyDecryptor::new(key)).is_err(),
6219                "Element plaintext must contain exactly one element: {invalid_element}"
6220            );
6221        }
6222
6223        let content = encrypted_gcm_element(
6224            "http://www.w3.org/2001/04/xmlenc#Content",
6225            "plaintext",
6226            None,
6227            false,
6228            &key,
6229        );
6230        let with_dtd = format!(
6231            "<!DOCTYPE root [<!ATTLIST root Id ID #IMPLIED>]><root xmlns:xenc=\"{XMLENC_NS}\">{content}</root>"
6232        );
6233        assert!(matches!(
6234            decrypt_document(&with_dtd, None, &SymmetricKeyDecryptor::new(key)),
6235            Err(XmlEncError::XmlParse(
6236                crate::xml::dom::ParseError::DtdDetected
6237            ))
6238        ));
6239        let mut policy = crate::policy::DecryptionPolicy::default();
6240        policy.xml.allow_internal_dtd = true;
6241        assert!(
6242            DecryptContext::new(&SymmetricKeyDecryptor::new(key))
6243                .policy(policy)
6244                .decrypt_document(&with_dtd, None)
6245                .expect("explicit internal-DTD opt-in must decrypt")
6246                .contains("plaintext")
6247        );
6248    }
6249
6250    #[test]
6251    fn rejects_plaintext_markup_that_crosses_the_encrypted_region() {
6252        // Parsing only after raw splicing is insufficient: balanced close/reopen
6253        // tags can keep the document valid while moving attacker nodes outside the
6254        // element whose encrypted child is being replaced.
6255        let key = [0x36_u8; 16];
6256        let crossing_markup = "</parent><attacker/><parent>";
6257        for type_uri in [
6258            "http://www.w3.org/2001/04/xmlenc#Content",
6259            "http://www.w3.org/2001/04/xmlenc#Element",
6260        ] {
6261            let encrypted = encrypted_gcm_element(type_uri, crossing_markup, None, false, &key);
6262            let document =
6263                format!("<outer xmlns:xenc=\"{XMLENC_NS}\"><parent>{encrypted}</parent></outer>");
6264            assert!(
6265                decrypt_document(&document, None, &SymmetricKeyDecryptor::new(key)).is_err(),
6266                "{type_uri} plaintext must not escape its replacement boundary"
6267            );
6268        }
6269    }
6270
6271    #[test]
6272    fn document_decryption_applies_byte_and_node_policy_before_parsing() {
6273        // Caller-owned XML must meet the compiled resource policy before the
6274        // initial DOM allocation; reparsed output uses the same node ceiling.
6275        let key = [0x38_u8; 16];
6276        let encrypted = encrypted_gcm_element(
6277            "http://www.w3.org/2001/04/xmlenc#Content",
6278            "plaintext",
6279            None,
6280            false,
6281            &key,
6282        );
6283        let document = format!("<root xmlns:xenc=\"{XMLENC_NS}\"><a/>{encrypted}</root>");
6284        let byte_policy = crate::policy::DecryptionPolicy {
6285            resources: crate::policy::ResourcePolicy {
6286                max_xml_document_bytes: document.len() - 1,
6287                ..crate::policy::ResourcePolicy::default()
6288            },
6289            ..crate::policy::DecryptionPolicy::default()
6290        };
6291        assert!(matches!(
6292            DecryptContext::new(&SymmetricKeyDecryptor::new(key))
6293                .policy(byte_policy)
6294                .decrypt_document(&document, None),
6295            Err(XmlEncError::Policy(crate::policy::PolicyViolation::ResourceLimit {
6296                resource: crate::policy::resource_name::XML_DOCUMENT,
6297                maximum,
6298                actual,
6299            })) if maximum == document.len() - 1 && actual == document.len()
6300        ));
6301
6302        let node_policy = crate::policy::DecryptionPolicy {
6303            resources: crate::policy::ResourcePolicy {
6304                max_xml_nodes: 3,
6305                ..crate::policy::ResourcePolicy::default()
6306            },
6307            ..crate::policy::DecryptionPolicy::default()
6308        };
6309        assert!(matches!(
6310            DecryptContext::new(&SymmetricKeyDecryptor::new(key))
6311                .policy(node_policy)
6312                .decrypt_document(&document, None),
6313            Err(XmlEncError::Policy(
6314                crate::policy::PolicyViolation::ResourceLimit {
6315                    resource: crate::policy::resource_name::XML_NODES,
6316                    maximum: 3,
6317                    actual: 4,
6318                }
6319            ))
6320        ));
6321    }
6322
6323    #[test]
6324    fn decryption_entry_points_enforce_policy_depth() {
6325        // Depth validation precedes EncryptedData selection for both borrowed
6326        // and retained documents, including inputs parsed under wider defaults.
6327        let xml = "<root><child><leaf/></child></root>";
6328        let policy = crate::policy::DecryptionPolicy {
6329            resources: crate::policy::ResourcePolicy {
6330                max_xml_depth: 2,
6331                ..crate::policy::ResourcePolicy::default()
6332            },
6333            ..crate::policy::DecryptionPolicy::default()
6334        };
6335        let resolver = SymmetricKeyDecryptor::new([0_u8; 16]);
6336        let mut document = XmlDocument::parse(xml).expect("wide retained fixture must parse");
6337
6338        assert!(matches!(
6339            DecryptContext::new(&resolver)
6340                .policy(policy.clone())
6341                .decrypt_document(xml, None),
6342            Err(XmlEncError::Policy(
6343                crate::policy::PolicyViolation::ResourceLimit {
6344                    resource: crate::policy::resource_name::XML_DEPTH,
6345                    maximum: 2,
6346                    actual: 3,
6347                }
6348            ))
6349        ));
6350        assert!(matches!(
6351            DecryptContext::new(&resolver)
6352                .policy(policy)
6353                .decrypt_owned_document(&mut document, None),
6354            Err(XmlEncError::Policy(
6355                crate::policy::PolicyViolation::ResourceLimit {
6356                    resource: crate::policy::resource_name::XML_DEPTH,
6357                    maximum: 2,
6358                    actual: 3,
6359                }
6360            ))
6361        ));
6362    }
6363
6364    #[test]
6365    fn fragment_validation_does_not_charge_its_internal_wrapper_node() {
6366        // The caller's node ceiling applies to input and output XML, not the
6367        // implementation-only element used to prove replacement boundaries.
6368        let key = [0x39_u8; 16];
6369        let plaintext = "<item/>".repeat(20);
6370        let encrypted = encrypted_gcm_element(
6371            "http://www.w3.org/2001/04/xmlenc#Content",
6372            &plaintext,
6373            None,
6374            false,
6375            &key,
6376        );
6377        let document = format!("<root xmlns:xenc=\"{XMLENC_NS}\">{encrypted}</root>");
6378        let resolver = SymmetricKeyDecryptor::new(key);
6379        let expected = decrypt_document(&document, None, &resolver)
6380            .expect("unbounded setup decryption must succeed");
6381        let exact_output_nodes = Document::parse(&expected)
6382            .expect("decrypted output must parse")
6383            .descendants()
6384            .count();
6385        let policy = crate::policy::DecryptionPolicy {
6386            resources: crate::policy::ResourcePolicy {
6387                max_xml_nodes: exact_output_nodes,
6388                ..crate::policy::ResourcePolicy::default()
6389            },
6390            ..crate::policy::DecryptionPolicy::default()
6391        };
6392
6393        assert_eq!(
6394            DecryptContext::new(&resolver)
6395                .policy(policy)
6396                .decrypt_document(&document, None)
6397                .expect("temporary wrapper must not consume caller node budget"),
6398            expected
6399        );
6400    }
6401
6402    #[test]
6403    fn owned_decryption_rejects_projected_node_limit_atomically() {
6404        // The owned document may have been parsed under a broader ceiling than
6405        // this operation; expanded plaintext must be bounded before mutation.
6406        let key = [0x3a_u8; 16];
6407        let plaintext = "<item/>".repeat(64);
6408        let encrypted = encrypted_gcm_element(
6409            "http://www.w3.org/2001/04/xmlenc#Content",
6410            &plaintext,
6411            None,
6412            false,
6413            &key,
6414        );
6415        let mut document = XmlDocument::parse(format!(
6416            "<root xmlns:xenc=\"{XMLENC_NS}\">{encrypted}</root>"
6417        ))
6418        .expect("owned encrypted fixture must parse");
6419        let input_nodes = document.with_view(|view| view.node_count());
6420        let before = document.as_xml().to_owned();
6421        let policy = crate::policy::DecryptionPolicy {
6422            resources: crate::policy::ResourcePolicy {
6423                max_xml_nodes: input_nodes,
6424                ..crate::policy::ResourcePolicy::default()
6425            },
6426            ..crate::policy::DecryptionPolicy::default()
6427        };
6428
6429        let error = DecryptContext::new(&SymmetricKeyDecryptor::new(key))
6430            .policy(policy)
6431            .decrypt_owned_document(&mut document, None)
6432            .expect_err("expanded plaintext must exceed the operation node ceiling");
6433
6434        assert!(matches!(
6435            error,
6436            XmlEncError::Policy(crate::policy::PolicyViolation::ResourceLimit {
6437                resource: crate::policy::resource_name::XML_NODES,
6438                maximum,
6439                ..
6440            }) if maximum == input_nodes
6441        ));
6442        assert_eq!(document.as_xml(), before);
6443        assert_eq!(document.generation(), 0);
6444    }
6445
6446    #[test]
6447    fn owned_decryption_reports_decrypted_depth_as_policy() {
6448        // The encrypted envelope can satisfy the active depth policy while its
6449        // plaintext replacement exceeds it. That rejection must retain the
6450        // typed policy contract and leave the owned document untouched.
6451        let key = [0x3b_u8; 16];
6452        let plaintext = format!("{}value{}", "<nested>".repeat(32), "</nested>".repeat(32));
6453        let encrypted = encrypted_gcm_element(
6454            "http://www.w3.org/2001/04/xmlenc#Element",
6455            &plaintext,
6456            None,
6457            false,
6458            &key,
6459        );
6460        let mut document = XmlDocument::parse(format!(
6461            "<root xmlns:xenc=\"{XMLENC_NS}\">{encrypted}</root>"
6462        ))
6463        .expect("encrypted fixture must parse");
6464        let input_depth = document.with_view(|view| view.max_depth());
6465        let before = document.as_xml().to_owned();
6466        let policy = crate::policy::DecryptionPolicy {
6467            resources: crate::policy::ResourcePolicy {
6468                max_xml_depth: input_depth,
6469                ..crate::policy::ResourcePolicy::default()
6470            },
6471            ..crate::policy::DecryptionPolicy::default()
6472        };
6473
6474        let error = DecryptContext::new(&SymmetricKeyDecryptor::new(key))
6475            .policy(policy)
6476            .decrypt_owned_document(&mut document, None)
6477            .expect_err("deep plaintext must exceed the active depth policy");
6478
6479        assert!(matches!(
6480            error,
6481            XmlEncError::Policy(crate::policy::PolicyViolation::ResourceLimit {
6482                resource: crate::policy::resource_name::XML_DEPTH,
6483                maximum,
6484                actual,
6485            }) if maximum == input_depth && actual > maximum
6486        ));
6487        assert_eq!(document.as_xml(), before);
6488        assert_eq!(document.generation(), 0);
6489    }
6490
6491    #[test]
6492    fn validates_replacement_plaintext_in_its_namespace_context() {
6493        // Decrypted fragments inherit namespaces from the encrypted node's
6494        // ancestors, so boundary validation must occur inside the source document.
6495        let key = [0x37_u8; 16];
6496        let encrypted = encrypted_gcm_element(
6497            "http://www.w3.org/2001/04/xmlenc#Content",
6498            "<shared:child/>",
6499            None,
6500            false,
6501            &key,
6502        );
6503        let document = format!(
6504            "<root xmlns:xenc=\"{XMLENC_NS}\" xmlns:shared=\"urn:shared\">{encrypted}</root>"
6505        );
6506        let decrypted = decrypt_document(&document, None, &SymmetricKeyDecryptor::new(key))
6507            .expect("inherited namespace prefixes must remain valid");
6508        assert_eq!(
6509            decrypted,
6510            format!(
6511                "<root xmlns:xenc=\"{XMLENC_NS}\" xmlns:shared=\"urn:shared\"><shared:child/></root>"
6512            )
6513        );
6514    }
6515
6516    fn encrypted_gcm_element(
6517        type_uri: &str,
6518        plaintext: &str,
6519        id: Option<&str>,
6520        declare_namespace: bool,
6521        key: &[u8; 16],
6522    ) -> String {
6523        let nonce = [0x44_u8; 12];
6524        let mut ciphertext = plaintext.as_bytes().to_vec();
6525        Aes128Gcm::new_from_slice(key)
6526            .expect("fixed content key length")
6527            .encrypt_in_place(&nonce.into(), b"", &mut ciphertext)
6528            .expect("test encryption must succeed");
6529        let mut wire = nonce.to_vec();
6530        wire.extend_from_slice(&ciphertext);
6531        let namespace = declare_namespace
6532            .then_some(format!(" xmlns:xenc=\"{XMLENC_NS}\""))
6533            .unwrap_or_default();
6534        let data_type = (!type_uri.is_empty())
6535            .then_some(format!(" Type=\"{type_uri}\""))
6536            .unwrap_or_default();
6537        let id = id
6538            .map(|value| format!(" Id=\"{value}\""))
6539            .unwrap_or_default();
6540        format!(
6541            "<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>",
6542            STANDARD.encode(wire)
6543        )
6544    }
6545}